/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Tintri by DDN, Inc. All rights reserved. */ /* * NETR SamLogon and SamLogoff RPC client functions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint32_t netlogon_logon(smb_logon_t *, smb_token_t *, smb_domainex_t *); static uint32_t netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *, smb_logon_t *, smb_token_t *); static void netr_invalidate_chain(void); static void netr_interactive_samlogon(netr_info_t *, smb_logon_t *, struct netr_logon_info1 *); static void netr_network_samlogon(ndr_heap_t *, netr_info_t *, smb_logon_t *, struct netr_logon_info2 *); static void netr_setup_identity(ndr_heap_t *, smb_logon_t *, netr_logon_id_t *); static uint32_t netr_setup_domain_groups(struct netr_validation_info3 *, smb_ids_t *); static uint32_t netr_setup_krb5res_groups(struct krb5_validation_info *, smb_ids_t *); static uint32_t netr_setup_token_wingrps(struct netr_validation_info3 *, smb_token_t *); /* * Shared with netr_auth.c */ extern netr_info_t netr_global_info; static mutex_t netlogon_mutex; static cond_t netlogon_cv; static boolean_t netlogon_busy = B_FALSE; static boolean_t netlogon_abort = B_FALSE; /* * Helper for Kerberos authentication */ uint32_t smb_decode_krb5_pac(smb_token_t *token, char *data, uint_t len) { struct krb5_validation_info info; ndr_buf_t *nbuf; smb_sid_t *domsid; uint32_t status = NT_STATUS_NO_MEMORY; int rc; bzero(&info, sizeof (info)); /* Need to keep this until we're done with &info */ nbuf = ndr_buf_init(&TYPEINFO(netr_interface)); if (nbuf == NULL) goto out; rc = ndr_buf_decode(nbuf, NDR_PTYPE_PAC, NETR_OPNUM_decode_krb5_pac, data, len, &info); if (rc != NDR_DRC_OK) { status = RPC_NT_PROTOCOL_ERROR; goto out; } /* * Copy the decoded info into the token, * similar to netr_setup_token() */ domsid = (smb_sid_t *)info.info3.LogonDomainId; token->tkn_user.i_sid = smb_sid_splice(domsid, info.info3.UserId); if (token->tkn_user.i_sid == NULL) goto out; token->tkn_primary_grp.i_sid = smb_sid_splice(domsid, info.info3.PrimaryGroupId); if (token->tkn_primary_grp.i_sid == NULL) goto out; if (info.info3.EffectiveName.str) { token->tkn_account_name = strdup((char *)info.info3.EffectiveName.str); if (token->tkn_account_name == NULL) goto out; } if (info.info3.LogonDomainName.str) { token->tkn_domain_name = strdup((char *)info.info3.LogonDomainName.str); if (token->tkn_domain_name == NULL) goto out; } status = netr_setup_domain_groups(&info.info3, &token->tkn_win_grps); if (status != NT_STATUS_SUCCESS) goto out; if (info.rg_rid_cnt != 0) { status = netr_setup_krb5res_groups(&info, &token->tkn_win_grps); if (status != NT_STATUS_SUCCESS) goto out; } status = netr_setup_token_wingrps(&info.info3, token); out: if (nbuf != NULL) ndr_buf_fini(nbuf); return (status); } /* * Abort impending domain logon requests. */ void smb_logon_abort(void) { (void) mutex_lock(&netlogon_mutex); if (netlogon_busy && !netlogon_abort) syslog(LOG_DEBUG, "logon abort"); netlogon_abort = B_TRUE; (void) cond_broadcast(&netlogon_cv); (void) mutex_unlock(&netlogon_mutex); } /* * This is the entry point for authenticating domain users. * * If we are not going to attempt to authenticate the user, * this function must return without updating the status. * * If the user is successfully authenticated, we build an * access token and the status will be NT_STATUS_SUCCESS. * Otherwise, the token contents are invalid. * * This will retry a few times for errors indicating that the * current DC might have gone off-line or become too busy etc. * With such errors, smb_ddiscover_bad_dc is called and then * the smb_domain_getinfo call here waits for new DC info. */ int smb_netr_logon_retries = 3; void smb_logon_domain(smb_logon_t *user_info, smb_token_t *token) { smb_domainex_t di; uint32_t status; int retries = smb_netr_logon_retries; if (user_info->lg_secmode != SMB_SECMODE_DOMAIN) return; if (user_info->lg_domain_type == SMB_DOMAIN_LOCAL) return; while (--retries > 0) { if (!smb_domain_getinfo(&di)) { syslog(LOG_ERR, "logon DC getinfo failed"); status = NT_STATUS_NO_LOGON_SERVERS; goto out; } (void) mutex_lock(&netlogon_mutex); while (netlogon_busy && !netlogon_abort) (void) cond_wait(&netlogon_cv, &netlogon_mutex); if (netlogon_abort) { (void) mutex_unlock(&netlogon_mutex); status = NT_STATUS_REQUEST_ABORTED; goto out; } netlogon_busy = B_TRUE; (void) mutex_unlock(&netlogon_mutex); status = netlogon_logon(user_info, token, &di); (void) mutex_lock(&netlogon_mutex); netlogon_busy = B_FALSE; if (netlogon_abort) status = NT_STATUS_REQUEST_ABORTED; (void) cond_signal(&netlogon_cv); (void) mutex_unlock(&netlogon_mutex); switch (status) { case NT_STATUS_BAD_NETWORK_PATH: case NT_STATUS_BAD_NETWORK_NAME: case RPC_NT_SERVER_TOO_BUSY: /* * May retry with a new DC, or if we're * out of retries, will return... */ status = NT_STATUS_NO_LOGON_SERVERS; break; default: goto out; } } out: if (status != NT_STATUS_SUCCESS) syslog(LOG_INFO, "logon[%s\\%s]: %s", user_info->lg_e_domain, user_info->lg_e_username, xlate_nt_status(status)); user_info->lg_status = status; } static uint32_t netr_get_handle(char *server, char *domain, mlsvc_handle_t *netr_handle) { uint32_t status; boolean_t did_renego = B_FALSE; reauth: if ((netr_global_info.flags & NETR_FLG_VALID) == 0 || !smb_match_netlogon_seqnum()) { /* * This does netr_server_req_challenge() and * netr_server_authenticate2(), updating the * current netlogon sequence number. */ status = netlogon_auth(server, domain, NETR_FLG_NULL); if (status != 0) { syslog(LOG_ERR, "%s: auth failed (%s)", __func__, xlate_nt_status(status)); return (status); } netr_global_info.flags |= NETR_FLG_VALID; } /* * This netr_open_secure call does the work to connect to the DC, * get the IPC share, open the named pipe, RPC bind, etc. */ status = netr_open_secure(server, domain, netr_handle); if (status != 0) { /* * This may have failed because the DC restarted. * Re-negotiate once. */ if (!did_renego) { did_renego = B_TRUE; netr_invalidate_chain(); syslog(LOG_ERR, "%s: open failed (%s); " "renegotiating...", __func__, xlate_nt_status(status)); goto reauth; } syslog(LOG_ERR, "%s: open failed (%s)", __func__, xlate_nt_status(status)); } return (status); } /* * Run a netr_server_samlogon call, dealing with the possible need to * re-establish the NetLogon credential chain. If that fails, return * NT_STATUS_DOMAIN_TRUST_INCONSISTENT indicating the machine account * needs it's password reset (or whatever). Other errors are from the * netr_server_samlogon() call including the many possibilities listed * above that function. */ uint32_t netlogon_logon(smb_logon_t *user_info, smb_token_t *token, smb_domainex_t *di) { char server[MAXHOSTNAMELEN]; mlsvc_handle_t netr_handle; uint32_t status; boolean_t did_reauth = B_FALSE; if (di->d_dci.dc_name[0] != '\0' && (*netr_global_info.server != '\0')) { (void) snprintf(server, sizeof (server), "\\\\%s", di->d_dci.dc_name); if (strncasecmp(netr_global_info.server, server, strlen(server)) != 0) netr_invalidate_chain(); } reauth: status = netr_get_handle(di->d_dci.dc_name, di->d_primary.di_nbname, &netr_handle); if (status != 0) { syslog(LOG_ERR, "%s: failed to get handle (%s)", __func__, xlate_nt_status(status)); return (NT_STATUS_DOMAIN_TRUST_INCONSISTENT); } status = netr_server_samlogon(&netr_handle, &netr_global_info, di->d_dci.dc_name, user_info, token); if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) { if (!did_reauth) { /* Call netlogon_auth() again, just once. */ (void) netr_close(&netr_handle); did_reauth = B_TRUE; goto reauth; } status = NT_STATUS_DOMAIN_TRUST_INCONSISTENT; } (void) netr_close(&netr_handle); return (status); } /* * Helper for mlsvc_netlogon * * Call netlogon_auth with appropriate locks etc. * Serialize like smb_logon_domain does for * netlogon_logon / netlogon_auth */ uint32_t smb_netlogon_check(char *server, char *domain) { mlsvc_handle_t netr_handle; uint32_t status; (void) mutex_lock(&netlogon_mutex); while (netlogon_busy) (void) cond_wait(&netlogon_cv, &netlogon_mutex); netlogon_busy = B_TRUE; (void) mutex_unlock(&netlogon_mutex); /* * Like netlogon_logon(), but no netr_server_samlogon call. * We're just making sure we can connect to the NETLOGON server. */ status = netr_get_handle(server, domain, &netr_handle); if (status == 0) (void) netr_close(&netr_handle); else syslog(LOG_ERR, "%s: failed to get handle (%s)", __func__, xlate_nt_status(status)); (void) mutex_lock(&netlogon_mutex); netlogon_busy = B_FALSE; (void) cond_signal(&netlogon_cv); (void) mutex_unlock(&netlogon_mutex); return (status); } static uint32_t netr_setup_token(struct netr_validation_info3 *info3, smb_logon_t *user_info, netr_info_t *netr_info, smb_token_t *token) { char *username, *domain; unsigned char rc4key[SMBAUTH_SESSION_KEY_SZ]; smb_sid_t *domsid; uint32_t status; char nbdomain[NETBIOS_NAME_SZ]; domsid = (smb_sid_t *)info3->LogonDomainId; token->tkn_user.i_sid = smb_sid_splice(domsid, info3->UserId); if (token->tkn_user.i_sid == NULL) return (NT_STATUS_NO_MEMORY); token->tkn_primary_grp.i_sid = smb_sid_splice(domsid, info3->PrimaryGroupId); if (token->tkn_primary_grp.i_sid == NULL) return (NT_STATUS_NO_MEMORY); username = (info3->EffectiveName.str) ? (char *)info3->EffectiveName.str : user_info->lg_e_username; if (info3->LogonDomainName.str) { domain = (char *)info3->LogonDomainName.str; } else if (*user_info->lg_e_domain != '\0') { domain = user_info->lg_e_domain; } else { (void) smb_getdomainname(nbdomain, sizeof (nbdomain)); domain = nbdomain; } if (username) token->tkn_account_name = strdup(username); if (domain) token->tkn_domain_name = strdup(domain); if (token->tkn_account_name == NULL || token->tkn_domain_name == NULL) return (NT_STATUS_NO_MEMORY); status = netr_setup_domain_groups(info3, &token->tkn_win_grps); if (status != NT_STATUS_SUCCESS) return (status); status = netr_setup_token_wingrps(info3, token); if (status != NT_STATUS_SUCCESS) return (status); /* * The UserSessionKey in NetrSamLogon RPC is obfuscated using the * session key obtained in the NETLOGON credential chain. * An 8 byte session key is zero extended to 16 bytes. This 16 byte * key is the key to the RC4 algorithm. The RC4 byte stream is * exclusively ored with the 16 byte UserSessionKey to recover * the the clear form. */ if ((token->tkn_ssnkey.val = malloc(SMBAUTH_SESSION_KEY_SZ)) == NULL) return (NT_STATUS_NO_MEMORY); token->tkn_ssnkey.len = SMBAUTH_SESSION_KEY_SZ; bzero(rc4key, SMBAUTH_SESSION_KEY_SZ); bcopy(netr_info->session_key.key, rc4key, netr_info->session_key.len); bcopy(info3->UserSessionKey.data, token->tkn_ssnkey.val, SMBAUTH_SESSION_KEY_SZ); rand_hash((unsigned char *)token->tkn_ssnkey.val, SMBAUTH_SESSION_KEY_SZ, rc4key, SMBAUTH_SESSION_KEY_SZ); return (NT_STATUS_SUCCESS); } /* * netr_server_samlogon * * NetrServerSamLogon RPC: interactive or network. It is assumed that * we have already authenticated with the PDC. If everything works, * we build a user info structure and return it, where the caller will * probably build an access token. * * Returns an NT status. There are numerous possibilities here. * For example: * NT_STATUS_INVALID_INFO_CLASS * NT_STATUS_INVALID_PARAMETER * NT_STATUS_ACCESS_DENIED * NT_STATUS_PASSWORD_MUST_CHANGE * NT_STATUS_NO_SUCH_USER * NT_STATUS_WRONG_PASSWORD * NT_STATUS_LOGON_FAILURE * NT_STATUS_ACCOUNT_RESTRICTION * NT_STATUS_INVALID_LOGON_HOURS * NT_STATUS_INVALID_WORKSTATION * NT_STATUS_INTERNAL_ERROR * NT_STATUS_PASSWORD_EXPIRED * NT_STATUS_ACCOUNT_DISABLED */ uint32_t netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info, char *server, smb_logon_t *user_info, smb_token_t *token) { struct netr_SamLogon logon_op; struct netr_SamLogonEx logon_ex_op; struct netr_authenticator auth; struct netr_authenticator ret_auth; struct netr_logon_info1 info1; struct netr_logon_info2 info2; struct netr_validation_info3 *info3; union netr_validation_u *valid_info; union netr_logon_info_u *logon_info; LPTSTR servername, hostname; ndr_heap_t *heap; int opnum; int rc, len; uint32_t status, *rpc_status; void *rpc_arg; /* * Should we get the server and hostname from netr_info? */ len = strlen(server) + 4; servername = ndr_rpc_malloc(netr_handle, len); hostname = ndr_rpc_malloc(netr_handle, NETBIOS_NAME_SZ); if (servername == NULL || hostname == NULL) { ndr_rpc_release(netr_handle); return (NT_STATUS_INTERNAL_ERROR); } (void) snprintf((char *)servername, len, "\\\\%s", server); if (smb_getnetbiosname((char *)hostname, NETBIOS_NAME_SZ) != 0) { ndr_rpc_release(netr_handle); return (NT_STATUS_INTERNAL_ERROR); } rc = netr_setup_authenticator(netr_info, &auth, &ret_auth); if (rc != SMBAUTH_SUCCESS) { ndr_rpc_release(netr_handle); return (NT_STATUS_INTERNAL_ERROR); } /* * If we use Secure RPC, we can use SamLogonEx instead of SamLogon. * SamLogonEx doesn't use NetLogon authenticators, instead relying * on Secure RPC to provide security. * This allows us to avoid being bitten by mitigations in * the authenticator verification logic on DCs. */ if (netr_info->use_logon_ex && (netr_info->nego_flags & NETR_NEGO_SECURE_RPC_FLAG) != 0) { bzero(&logon_ex_op, sizeof (struct netr_SamLogonEx)); logon_ex_op.servername = servername; logon_ex_op.hostname = hostname; logon_ex_op.logon_info.logon_level = user_info->lg_level; logon_ex_op.logon_info.switch_value = user_info->lg_level; logon_ex_op.validation_level = NETR_VALIDATION_LEVEL3; logon_ex_op.extra_flags = 0; logon_info = &logon_ex_op.logon_info.ru; valid_info = &logon_ex_op.ru; rpc_status = &logon_ex_op.status; rpc_arg = &logon_ex_op; opnum = NETR_OPNUM_SamLogonEx; } else { bzero(&logon_op, sizeof (struct netr_SamLogon)); logon_op.servername = servername; logon_op.hostname = hostname; logon_op.auth = &auth; logon_op.ret_auth = &ret_auth; logon_op.logon_info.logon_level = user_info->lg_level; logon_op.logon_info.switch_value = user_info->lg_level; logon_op.validation_level = NETR_VALIDATION_LEVEL3; logon_info = &logon_op.logon_info.ru; valid_info = &logon_op.ru; rpc_status = &logon_op.status; rpc_arg = &logon_op; opnum = NETR_OPNUM_SamLogon; } heap = ndr_rpc_get_heap(netr_handle); switch (user_info->lg_level) { case NETR_INTERACTIVE_LOGON: netr_setup_identity(heap, user_info, &info1.identity); netr_interactive_samlogon(netr_info, user_info, &info1); logon_info->info1 = &info1; break; case NETR_NETWORK_LOGON: if (user_info->lg_challenge_key.len < 8 || user_info->lg_challenge_key.val == NULL) { ndr_rpc_release(netr_handle); return (NT_STATUS_INVALID_PARAMETER); } netr_setup_identity(heap, user_info, &info2.identity); netr_network_samlogon(heap, netr_info, user_info, &info2); logon_info->info2 = &info2; break; default: ndr_rpc_release(netr_handle); return (NT_STATUS_INVALID_PARAMETER); } rc = ndr_rpc_call(netr_handle, opnum, rpc_arg); if (rc != 0) { bzero(netr_info, sizeof (netr_info_t)); status = NT_STATUS_INVALID_PARAMETER; } else if (*rpc_status != 0) { status = NT_SC_VALUE(*rpc_status); /* * We need to validate the chain even though we have * a non-zero status. If the status is ACCESS_DENIED * this will trigger a new credential chain. However, * a valid credential is returned with some status * codes; for example, WRONG_PASSWORD. * * SamLogonEx doesn't use authenticators - nothing to validate. */ if (rpc_arg == &logon_op) (void) netr_validate_chain(netr_info, logon_op.ret_auth); } else { if (rpc_arg == &logon_op) { status = netr_validate_chain(netr_info, logon_op.ret_auth); if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) { ndr_rpc_release(netr_handle); return (status); } } info3 = valid_info->info3; status = netr_setup_token(info3, user_info, netr_info, token); } ndr_rpc_release(netr_handle); return (status); } /* * netr_interactive_samlogon * * Set things up for an interactive SamLogon. Copy the NT and LM * passwords to the logon structure and hash them with the session * key. */ static void netr_interactive_samlogon(netr_info_t *netr_info, smb_logon_t *user_info, struct netr_logon_info1 *info1) { BYTE key[NETR_OWF_PASSWORD_SZ]; (void) memcpy(&info1->lm_owf_password, user_info->lg_lm_password.val, sizeof (netr_owf_password_t)); (void) memcpy(&info1->nt_owf_password, user_info->lg_nt_password.val, sizeof (netr_owf_password_t)); (void) memset(key, 0, NETR_OWF_PASSWORD_SZ); (void) memcpy(key, netr_info->session_key.key, netr_info->session_key.len); rand_hash((unsigned char *)&info1->lm_owf_password, NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ); rand_hash((unsigned char *)&info1->nt_owf_password, NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ); } /* * netr_network_samlogon * * Set things up for a network SamLogon. We provide a copy of the random * challenge, that we sent to the client, to the domain controller. This * is the key that the client will have used to encrypt the NT and LM * passwords. Note that Windows 9x clients may not provide both passwords. */ /*ARGSUSED*/ static void netr_network_samlogon(ndr_heap_t *heap, netr_info_t *netr_info, smb_logon_t *user_info, struct netr_logon_info2 *info2) { uint32_t len; if (user_info->lg_challenge_key.len >= 8 && user_info->lg_challenge_key.val != 0) { bcopy(user_info->lg_challenge_key.val, info2->lm_challenge.data, 8); } else { bzero(info2->lm_challenge.data, 8); } if ((len = user_info->lg_nt_password.len) != 0) { ndr_heap_mkvcb(heap, user_info->lg_nt_password.val, len, (ndr_vcbuf_t *)&info2->nt_response); } else { bzero(&info2->nt_response, sizeof (netr_vcbuf_t)); } if ((len = user_info->lg_lm_password.len) != 0) { ndr_heap_mkvcb(heap, user_info->lg_lm_password.val, len, (ndr_vcbuf_t *)&info2->lm_response); } else { bzero(&info2->lm_response, sizeof (netr_vcbuf_t)); } } /* * netr_setup_authenticator * * Set up the request and return authenticators. A new credential is * generated from the session key, the current client credential and * the current time, i.e. * * NewCredential = Cred(SessionKey, OldCredential, time); * * The timestamp, which is used as a random seed, is stored in both * the request and return authenticators. * * If any difficulties occur using the cryptographic framework, the * function returns SMBAUTH_FAILURE. Otherwise SMBAUTH_SUCCESS is * returned. */ int netr_setup_authenticator(netr_info_t *netr_info, struct netr_authenticator *auth, struct netr_authenticator *ret_auth) { int rc; bzero(auth, sizeof (struct netr_authenticator)); /* * Windows DCs will reject Authenticators if none of the first * 5 bytes of the ClientStoredCredential are unique. * Keep retrying until we've generated one that satisfies this. */ netr_info->timestamp = time(0) - 1; do { auth->timestamp = ++netr_info->timestamp; rc = netr_gen_credentials(netr_info->session_key.key, &netr_info->client_credential, netr_info->timestamp, (netr_cred_t *)&auth->credential, B_TRUE); if (rc != SMBAUTH_SUCCESS && rc != SMBAUTH_RETRY) return (SMBAUTH_FAILURE); } while (rc == SMBAUTH_RETRY); if (ret_auth) { bzero(ret_auth, sizeof (struct netr_authenticator)); ret_auth->timestamp = netr_info->timestamp; } return (SMBAUTH_SUCCESS); } /* * Validate the returned credentials and update the credential chain. * The server returns an updated client credential rather than a new * server credential. The server uses (timestamp + 1) when generating * the credential. * * Generate the new seed for the credential chain. The new seed is * formed by adding (timestamp + 1) to the current client credential. * The only quirk is the uint32_t style addition. * * Returns NT_STATUS_INSUFFICIENT_LOGON_INFO if auth->credential is a * NULL pointer. The Authenticator field of the SamLogon response packet * sent by the Samba 3 PDC always return NULL pointer if the received * SamLogon request is not immediately followed by the ServerReqChallenge * and ServerAuthenticate2 requests. * * Returns NT_STATUS_SUCCESS if the server returned a valid credential. * Otherwise we retirm NT_STATUS_UNSUCCESSFUL. */ uint32_t netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth) { netr_cred_t cred; uint32_t result = NT_STATUS_SUCCESS; uint32_t *dwp; ++netr_info->timestamp; if (netr_gen_credentials(netr_info->session_key.key, &netr_info->client_credential, netr_info->timestamp, &cred, B_FALSE) != SMBAUTH_SUCCESS) return (NT_STATUS_INTERNAL_ERROR); if (&auth->credential == 0) { /* * If the validation fails, destroy the credential chain. * This should trigger a new authentication chain. */ bzero(netr_info, sizeof (netr_info_t)); return (NT_STATUS_INSUFFICIENT_LOGON_INFO); } result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t)); if (result != 0) { /* * If the validation fails, destroy the credential chain. * This should trigger a new authentication chain. */ bzero(netr_info, sizeof (netr_info_t)); result = NT_STATUS_UNSUCCESSFUL; } else { /* * Otherwise generate the next step in the chain. */ /*LINTED E_BAD_PTR_CAST_ALIGN*/ dwp = (uint32_t *)&netr_info->client_credential; dwp[0] += netr_info->timestamp; netr_info->flags |= NETR_FLG_VALID; } return (result); } /* * netr_invalidate_chain * * Mark the credential chain as invalid so that it will be recreated * on the next attempt. */ static void netr_invalidate_chain(void) { netr_global_info.flags &= ~NETR_FLG_VALID; } /* * netr_setup_identity * * Set up the client identity information. All of this information is * specifically related to the client user and workstation attempting * to access this system. It may not be in our primary domain. * * I don't know what logon_id is, it seems to be a unique identifier. * Increment it before each use. */ static void netr_setup_identity(ndr_heap_t *heap, smb_logon_t *user_info, netr_logon_id_t *identity) { static mutex_t logon_id_mutex; static uint32_t logon_id; (void) mutex_lock(&logon_id_mutex); if (logon_id == 0) logon_id = 0xDCD0; ++logon_id; user_info->lg_logon_id = logon_id; (void) mutex_unlock(&logon_id_mutex); /* * [MS-APDS] 3.1.5.2 "NTLM Network Logon" says to set * ParameterControl to the 'E' + 'K' bits. Those are: * (1 << 5) | (1 << 11), a.k.a */ identity->parameter_control = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT; identity->logon_id.LowPart = logon_id; identity->logon_id.HighPart = 0; ndr_heap_mkvcs(heap, user_info->lg_domain, (ndr_vcstr_t *)&identity->domain_name); ndr_heap_mkvcs(heap, user_info->lg_username, (ndr_vcstr_t *)&identity->username); /* * Some systems prefix the client workstation name with \\. * It doesn't seem to make any difference whether it's there * or not. */ ndr_heap_mkvcs(heap, user_info->lg_workstation, (ndr_vcstr_t *)&identity->workstation); } /* * Add local and well-known group membership to the given * token. Called after domain groups have been added. */ static uint32_t netr_setup_token_wingrps(struct netr_validation_info3 *info3 __unused, smb_token_t *token) { uint32_t status; status = smb_sam_usr_groups(token->tkn_user.i_sid, &token->tkn_win_grps); if (status != NT_STATUS_SUCCESS) return (status); status = smb_wka_token_groups(token->tkn_flags, &token->tkn_win_grps); return (status); } /* * Converts groups information in the returned structure by domain controller * (info3) to an internal representation (gids) */ static uint32_t netr_setup_domain_groups(struct netr_validation_info3 *info3, smb_ids_t *gids) { smb_sid_t *domain_sid; smb_id_t *ids; int i, total_cnt; if ((i = info3->GroupCount) == 0) i++; i += info3->SidCount; total_cnt = gids->i_cnt + i; gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t)); if (gids->i_ids == NULL) return (NT_STATUS_NO_MEMORY); domain_sid = (smb_sid_t *)info3->LogonDomainId; ids = gids->i_ids + gids->i_cnt; for (i = 0; i < info3->GroupCount; i++, gids->i_cnt++, ids++) { ids->i_sid = smb_sid_splice(domain_sid, info3->GroupIds[i].rid); if (ids->i_sid == NULL) return (NT_STATUS_NO_MEMORY); ids->i_attrs = info3->GroupIds[i].attributes; } if (info3->GroupCount == 0) { /* * if there's no global group should add the primary group. */ ids->i_sid = smb_sid_splice(domain_sid, info3->PrimaryGroupId); if (ids->i_sid == NULL) return (NT_STATUS_NO_MEMORY); ids->i_attrs = 0x7; gids->i_cnt++; ids++; } /* Add the extra SIDs */ for (i = 0; i < info3->SidCount; i++, gids->i_cnt++, ids++) { ids->i_sid = smb_sid_dup((smb_sid_t *)info3->ExtraSids[i].sid); if (ids->i_sid == NULL) return (NT_STATUS_NO_MEMORY); ids->i_attrs = info3->ExtraSids[i].attributes; } return (NT_STATUS_SUCCESS); } /* * Converts additional "resource" groups (from krb5_validation_info) * into the internal representation (gids), appending to the list * already put in place by netr_setup_domain_groups(). */ static uint32_t netr_setup_krb5res_groups(struct krb5_validation_info *info, smb_ids_t *gids) { smb_sid_t *domain_sid; smb_id_t *ids; int i, total_cnt; total_cnt = gids->i_cnt + info->rg_rid_cnt; gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t)); if (gids->i_ids == NULL) return (NT_STATUS_NO_MEMORY); domain_sid = (smb_sid_t *)info->rg_dom_sid; ids = gids->i_ids + gids->i_cnt; for (i = 0; i < info->rg_rid_cnt; i++, gids->i_cnt++, ids++) { ids->i_sid = smb_sid_splice(domain_sid, info->rg_rids[i].rid); if (ids->i_sid == NULL) return (NT_STATUS_NO_MEMORY); ids->i_attrs = info->rg_rids[i].attributes; } return (0); }