/* * 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 2013 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include static int smb_authenticate(smb_request_t *, smb_arg_sessionsetup_t *); static int smb_authenticate_core(smb_request_t *, smb_arg_sessionsetup_t *); static uint32_t smb_priv_xlate(smb_token_t *); #ifdef _KERNEL static void smb_cred_set_sid(smb_id_t *id, ksid_t *ksid); static ksidlist_t *smb_cred_set_sidlist(smb_ids_t *token_grps); #endif /* _KERNEL */ /* * In NTLM 0.12, the padding between the Native OS and Native LM is a bit * strange. On NT4.0, there is a 2 byte pad between the OS (Windows NT 1381) * and LM (Windows NT 4.0). On Windows 2000, there is no padding between * the OS (Windows 2000 2195) and LM (Windows 2000 5.0). * If the padding is removed from the decode string the NT4.0 LM comes out * as an empty string. So if the client's native OS is Win NT we consider * the padding otherwise we don't. * * For Pre-NTLM 0.12, despite the CIFS/1.0 spec, the user and domain are * not always present in the message. We try to get the account name and * the primary domain but we don't care about the the native OS or native * LM fields. * * If the Native LM cannot be determined, default to Windows NT. */ smb_sdrc_t smb_pre_session_setup_andx(smb_request_t *sr) { smb_arg_sessionsetup_t *sinfo; char *native_os; char *native_lm; uint32_t junk_sesskey; uint16_t maxbufsize; uint16_t vcnumber; int rc = 0; sinfo = smb_srm_zalloc(sr, sizeof (smb_arg_sessionsetup_t)); sr->sr_ssetup = sinfo; if (sr->session->dialect >= NT_LM_0_12) { rc = smbsr_decode_vwv(sr, "b.wwwwlww4.l", &sr->andx_com, &sr->andx_off, &maxbufsize, &sinfo->ssi_maxmpxcount, &vcnumber, &junk_sesskey, &sinfo->ssi_cipwlen, &sinfo->ssi_cspwlen, &sinfo->ssi_capabilities); if (rc != 0) goto pre_session_setup_andx_done; sinfo->ssi_cipwd = smb_srm_zalloc(sr, sinfo->ssi_cipwlen + 1); sinfo->ssi_cspwd = smb_srm_zalloc(sr, sinfo->ssi_cspwlen + 1); rc = smbsr_decode_data(sr, "%#c#cuuu", sr, sinfo->ssi_cipwlen, sinfo->ssi_cipwd, sinfo->ssi_cspwlen, sinfo->ssi_cspwd, &sinfo->ssi_user, &sinfo->ssi_domain, &native_os); if (rc != 0) goto pre_session_setup_andx_done; sinfo->ssi_cipwd[sinfo->ssi_cipwlen] = 0; sinfo->ssi_cspwd[sinfo->ssi_cspwlen] = 0; sr->session->native_os = smbnative_os_value(native_os); if (sr->session->native_os == NATIVE_OS_WINNT) rc = smbsr_decode_data(sr, "%,u", sr, &native_lm); else rc = smbsr_decode_data(sr, "%u", sr, &native_lm); if (rc != 0 || native_lm == NULL) native_lm = "NT LAN Manager 4.0"; sr->session->native_lm = smbnative_lm_value(native_lm); } else { rc = smbsr_decode_vwv(sr, "b.wwwwlw4.", &sr->andx_com, &sr->andx_off, &maxbufsize, &sinfo->ssi_maxmpxcount, &vcnumber, &junk_sesskey, &sinfo->ssi_cipwlen); if (rc != 0) goto pre_session_setup_andx_done; sinfo->ssi_cipwd = smb_srm_zalloc(sr, sinfo->ssi_cipwlen + 1); rc = smbsr_decode_data(sr, "%#c", sr, sinfo->ssi_cipwlen, sinfo->ssi_cipwd); if (rc != 0) goto pre_session_setup_andx_done; sinfo->ssi_cipwd[sinfo->ssi_cipwlen] = 0; if (smbsr_decode_data(sr, "%u", sr, &sinfo->ssi_user) != 0) sinfo->ssi_user = ""; if (smbsr_decode_data(sr, "%u", sr, &sinfo->ssi_domain) != 0) sinfo->ssi_domain = ""; native_lm = "NT LAN Manager 4.0"; sr->session->native_os = NATIVE_OS_WINNT; sr->session->native_lm = smbnative_lm_value(native_lm); } sr->session->vcnumber = vcnumber; sr->session->smb_msg_size = maxbufsize; pre_session_setup_andx_done: DTRACE_SMB_2(op__SessionSetupX__start, smb_request_t *, sr, smb_arg_sessionsetup_t, sinfo); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } void smb_post_session_setup_andx(smb_request_t *sr) { smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup; DTRACE_SMB_2(op__SessionSetupX__done, smb_request_t *, sr, smb_arg_sessionsetup_t, sinfo); if (sinfo->ssi_cipwd != NULL) bzero(sinfo->ssi_cipwd, sinfo->ssi_cipwlen + 1); if (sinfo->ssi_cspwd != NULL) bzero(sinfo->ssi_cspwd, sinfo->ssi_cspwlen + 1); } /* * If signing has not already been enabled on this session check to see if * it should be enabled. The first authenticated logon provides the MAC * key and sequence numbers for signing all subsequent sessions on the same * connection. * * NT systems use different native OS and native LanMan values dependent on * whether they are acting as a client or a server. NT 4.0 server responds * with the following values: * * NativeOS: Windows NT 4.0 * NativeLM: NT LAN Manager 4.0 */ smb_sdrc_t smb_com_session_setup_andx(smb_request_t *sr) { smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup; int rc; if (smb_authenticate(sr, sinfo) != 0) return (SDRC_ERROR); if (sr->session->native_lm == NATIVE_LM_WIN2000) sinfo->ssi_capabilities |= CAP_LARGE_FILES | CAP_LARGE_READX | CAP_LARGE_WRITEX; if (!smb_oplock_levelII) sr->session->capabilities &= ~CAP_LEVEL_II_OPLOCKS; sr->session->capabilities = sinfo->ssi_capabilities; rc = smbsr_encode_result(sr, 3, VAR_BCC, "bb.www%uuu", 3, sr->andx_com, -1, /* andx_off */ sinfo->ssi_guest ? 1 : 0, VAR_BCC, sr, smbnative_os_str(&sr->sr_cfg->skc_version), smbnative_lm_str(&sr->sr_cfg->skc_version), sr->sr_cfg->skc_nbdomain); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } static int smb_authenticate(smb_request_t *sr, smb_arg_sessionsetup_t *sinfo) { int rc; smb_server_t *sv = sr->sr_server; if (smb_threshold_enter(&sv->sv_ssetup_ct) != 0) { smbsr_error(sr, RPC_NT_SERVER_TOO_BUSY, 0, 0); return (-1); } rc = smb_authenticate_core(sr, sinfo); smb_threshold_exit(&sv->sv_ssetup_ct); return (rc); } /* * Authenticate a user. If the user has already been authenticated on * this session, we can simply dup the user and return. * * Otherwise, the user information is passed to smbd for authentication. * If smbd can authenticate the user an access token is returned and we * generate a cred and new user based on the token. */ static int smb_authenticate_core(smb_request_t *sr, smb_arg_sessionsetup_t *sinfo) { char *hostname = sr->sr_cfg->skc_hostname; int security = sr->sr_cfg->skc_secmode; smb_token_t *token = NULL; smb_user_t *user = NULL; smb_logon_t user_info; boolean_t need_lookup = B_FALSE; uint32_t privileges; cred_t *cr; char *buf = NULL; char *p; bzero(&user_info, sizeof (smb_logon_t)); user_info.lg_e_domain = sinfo->ssi_domain; if ((*sinfo->ssi_user == '\0') && (sinfo->ssi_cspwlen == 0) && (sinfo->ssi_cipwlen == 0 || (sinfo->ssi_cipwlen == 1 && *sinfo->ssi_cipwd == '\0'))) { user_info.lg_e_username = "anonymous"; user_info.lg_flags |= SMB_ATF_ANON; } else { user_info.lg_e_username = sinfo->ssi_user; } /* * Handle user@domain format. We need to retain the original * data as this is important in some forms of authentication. */ if (*sinfo->ssi_domain == '\0') { buf = smb_srm_strdup(sr, sinfo->ssi_user); if ((p = strchr(buf, '@')) != NULL) { *p = '\0'; user_info.lg_e_username = buf; user_info.lg_e_domain = p + 1; } } /* * If no domain name has been provided in domain mode we cannot * determine if this is a local user or a domain user without * obtaining an access token. So we postpone the lookup until * after authentication. */ if (security == SMB_SECMODE_WORKGRP) { user = smb_session_dup_user(sr->session, hostname, user_info.lg_e_username); } else if (*user_info.lg_e_domain != '\0') { user = smb_session_dup_user(sr->session, user_info.lg_e_domain, user_info.lg_e_username); } else { need_lookup = B_TRUE; } if (user != NULL) { sinfo->ssi_guest = SMB_USER_IS_GUEST(user); sr->user_cr = user->u_cred; sr->smb_uid = user->u_uid; sr->uid_user = user; return (0); } user_info.lg_level = NETR_NETWORK_LOGON; user_info.lg_domain = sinfo->ssi_domain; user_info.lg_username = sinfo->ssi_user; user_info.lg_workstation = sr->session->workstation; user_info.lg_clnt_ipaddr = sr->session->ipaddr; user_info.lg_local_ipaddr = sr->session->local_ipaddr; user_info.lg_local_port = sr->session->s_local_port; user_info.lg_challenge_key.val = sr->session->challenge_key; user_info.lg_challenge_key.len = sr->session->challenge_len; user_info.lg_nt_password.val = sinfo->ssi_cspwd; user_info.lg_nt_password.len = sinfo->ssi_cspwlen; user_info.lg_lm_password.val = sinfo->ssi_cipwd; user_info.lg_lm_password.len = sinfo->ssi_cipwlen; user_info.lg_native_os = sr->session->native_os; user_info.lg_native_lm = sr->session->native_lm; DTRACE_PROBE1(smb__sessionsetup__clntinfo, smb_logon_t *, &user_info); if ((token = smb_get_token(sr->session, &user_info)) == NULL) { smbsr_error(sr, 0, ERRSRV, ERRbadpw); return (-1); } if (need_lookup) { user = smb_session_dup_user(sr->session, token->tkn_domain_name, token->tkn_account_name); if (user != NULL) { sinfo->ssi_guest = SMB_USER_IS_GUEST(user); sr->user_cr = user->u_cred; sr->smb_uid = user->u_uid; sr->uid_user = user; smb_token_free(token); return (0); } } if ((cr = smb_cred_create(token)) == NULL) { smb_token_free(token); smbsr_error(sr, 0, ERRDOS, ERROR_INVALID_HANDLE); return (-1); } privileges = smb_priv_xlate(token); user = smb_user_login(sr->session, cr, token->tkn_domain_name, token->tkn_account_name, token->tkn_flags, privileges, token->tkn_audit_sid); crfree(cr); /* * Save the session key, and (maybe) enable signing, * but only for real logon (not ANON or GUEST). */ if ((token->tkn_flags & (SMB_ATF_GUEST | SMB_ATF_ANON)) == 0) (void) smb_sign_begin(sr, token); smb_token_free(token); if (user == NULL) { smbsr_error(sr, 0, ERRDOS, ERROR_INVALID_HANDLE); return (-1); } sinfo->ssi_guest = SMB_USER_IS_GUEST(user); sr->user_cr = user->u_cred; sr->smb_uid = user->u_uid; sr->uid_user = user; return (0); } #ifdef _KERNEL /* * Allocate a Solaris cred and initialize it based on the access token. * * If the user can be mapped to a non-ephemeral ID, the cred gid is set * to the Solaris user's primary group. * * If the mapped UID is ephemeral, or the primary group could not be * obtained, the cred gid is set to whatever Solaris group is mapped * to the token's primary group. */ cred_t * smb_cred_create(smb_token_t *token) { ksid_t ksid; ksidlist_t *ksidlist = NULL; smb_posix_grps_t *posix_grps; cred_t *cr; gid_t gid; ASSERT(token); ASSERT(token->tkn_posix_grps); posix_grps = token->tkn_posix_grps; cr = crget(); ASSERT(cr != NULL); if (!IDMAP_ID_IS_EPHEMERAL(token->tkn_user.i_id) && (posix_grps->pg_ngrps != 0)) { gid = posix_grps->pg_grps[0]; } else { gid = token->tkn_primary_grp.i_id; } if (crsetugid(cr, token->tkn_user.i_id, gid) != 0) { crfree(cr); return (NULL); } if (crsetgroups(cr, posix_grps->pg_ngrps, posix_grps->pg_grps) != 0) { crfree(cr); return (NULL); } smb_cred_set_sid(&token->tkn_user, &ksid); crsetsid(cr, &ksid, KSID_USER); smb_cred_set_sid(&token->tkn_primary_grp, &ksid); crsetsid(cr, &ksid, KSID_GROUP); smb_cred_set_sid(&token->tkn_owner, &ksid); crsetsid(cr, &ksid, KSID_OWNER); ksidlist = smb_cred_set_sidlist(&token->tkn_win_grps); crsetsidlist(cr, ksidlist); /* * In the AD world, "take ownership privilege" is very much * like having Unix "root" privileges. It's normally given * to members of the "Administrators" group, which normally * includes the the local Administrator (like root) and when * joined to a domain, "Domain Admins". */ if (smb_token_query_privilege(token, SE_TAKE_OWNERSHIP_LUID)) { (void) crsetpriv(cr, PRIV_FILE_CHOWN, PRIV_FILE_DAC_READ, PRIV_FILE_DAC_SEARCH, PRIV_FILE_DAC_WRITE, PRIV_FILE_OWNER, NULL); } return (cr); } /* * Initialize the ksid based on the given smb_id_t. */ static void smb_cred_set_sid(smb_id_t *id, ksid_t *ksid) { char sidstr[SMB_SID_STRSZ]; int rc; ASSERT(id); ASSERT(id->i_sid); ksid->ks_id = id->i_id; smb_sid_tostr(id->i_sid, sidstr); rc = smb_sid_splitstr(sidstr, &ksid->ks_rid); ASSERT(rc == 0); ksid->ks_attr = id->i_attrs; ksid->ks_domain = ksid_lookupdomain(sidstr); } /* * Allocate and initialize the ksidlist based on the access token group list. */ static ksidlist_t * smb_cred_set_sidlist(smb_ids_t *token_grps) { int i; ksidlist_t *lp; lp = kmem_zalloc(KSIDLIST_MEM(token_grps->i_cnt), KM_SLEEP); lp->ksl_ref = 1; lp->ksl_nsid = token_grps->i_cnt; lp->ksl_neid = 0; for (i = 0; i < lp->ksl_nsid; i++) { smb_cred_set_sid(&token_grps->i_ids[i], &lp->ksl_sids[i]); if (lp->ksl_sids[i].ks_id > IDMAP_WK__MAX_GID) lp->ksl_neid++; } return (lp); } #endif /* _KERNEL */ /* * Convert access token privileges to local definitions. */ static uint32_t smb_priv_xlate(smb_token_t *token) { uint32_t privileges = 0; if (smb_token_query_privilege(token, SE_BACKUP_LUID)) privileges |= SMB_USER_PRIV_BACKUP; if (smb_token_query_privilege(token, SE_RESTORE_LUID)) privileges |= SMB_USER_PRIV_RESTORE; if (smb_token_query_privilege(token, SE_TAKE_OWNERSHIP_LUID)) privileges |= SMB_USER_PRIV_TAKE_OWNERSHIP; if (smb_token_query_privilege(token, SE_SECURITY_LUID)) privileges |= SMB_USER_PRIV_SECURITY; return (privileges); }