#include <sys/types.h>
#include <sys/sid.h>
#include <sys/priv_names.h>
#include <sys/priv.h>
#include <sys/policy.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_door.h>
#define ADMINISTRATORS_SID "S-1-5-32-544"
#define SMB_USER_SSNID(u) \
((uintptr_t)&smb_cache_user ^ (uintptr_t)(u))
static void smb_user_delete(void *);
static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *);
static void smb_user_auth_logoff(smb_user_t *);
static void smb_user_logoff_tq(void *);
smb_user_t *
smb_user_new(smb_session_t *session)
{
smb_user_t *user;
uint_t gen;
uint32_t ucount;
ASSERT(session);
ASSERT(session->s_magic == SMB_SESSION_MAGIC);
user = kmem_cache_alloc(smb_cache_user, KM_SLEEP);
gen = (user->u_ssnid + 1) & 7;
bzero(user, sizeof (smb_user_t));
user->u_refcnt = 1;
user->u_session = session;
user->u_server = session->s_server;
user->u_logon_time = gethrestime_sec();
if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid))
goto errout;
user->u_ssnid = SMB_USER_SSNID(user) + gen;
mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL);
user->u_state = SMB_USER_STATE_LOGGING_ON;
user->u_magic = SMB_USER_MAGIC;
smb_llist_enter(&session->s_user_list, RW_WRITER);
ucount = smb_llist_get_count(&session->s_user_list);
smb_llist_insert_tail(&session->s_user_list, user);
smb_llist_exit(&session->s_user_list);
smb_server_inc_users(session->s_server);
if (ucount == 0) {
timeout_id_t tmo = NULL;
smb_rwx_rwenter(&session->s_lock, RW_WRITER);
tmo = session->s_auth_tmo;
session->s_auth_tmo = NULL;
smb_rwx_rwexit(&session->s_lock);
if (tmo != NULL)
(void) untimeout(tmo);
}
return (user);
errout:
if (user->u_uid != 0)
smb_idpool_free(&session->s_uid_pool, user->u_uid);
kmem_cache_free(smb_cache_user, user);
return (NULL);
}
int
smb_user_logon(
smb_user_t *user,
cred_t *cr,
char *domain_name,
char *account_name,
uint32_t flags,
uint32_t privileges,
uint32_t audit_sid)
{
ksocket_t authsock = NULL;
timeout_id_t tmo = NULL;
ASSERT(user->u_magic == SMB_USER_MAGIC);
ASSERT(cr);
ASSERT(account_name);
ASSERT(domain_name);
mutex_enter(&user->u_mutex);
if (user->u_state != SMB_USER_STATE_LOGGING_ON) {
mutex_exit(&user->u_mutex);
return (-1);
}
authsock = user->u_authsock;
user->u_authsock = NULL;
tmo = user->u_auth_tmo;
user->u_auth_tmo = NULL;
user->u_state = SMB_USER_STATE_LOGGED_ON;
user->u_flags = flags;
user->u_name_len = strlen(account_name) + 1;
user->u_domain_len = strlen(domain_name) + 1;
user->u_name = smb_mem_strdup(account_name);
user->u_domain = smb_mem_strdup(domain_name);
user->u_audit_sid = audit_sid;
smb_user_setcred(user, cr, privileges);
mutex_exit(&user->u_mutex);
if (tmo != NULL)
(void) untimeout(tmo);
if (authsock != NULL)
smb_authsock_close(user, authsock);
return (0);
}
void
smb_user_logoff(
smb_user_t *user)
{
ksocket_t authsock = NULL;
timeout_id_t tmo = NULL;
ASSERT(user->u_magic == SMB_USER_MAGIC);
mutex_enter(&user->u_mutex);
ASSERT(user->u_refcnt);
switch (user->u_state) {
case SMB_USER_STATE_LOGGING_ON:
authsock = user->u_authsock;
user->u_authsock = NULL;
tmo = user->u_auth_tmo;
user->u_auth_tmo = NULL;
user->u_state = SMB_USER_STATE_LOGGING_OFF;
mutex_exit(&user->u_mutex);
if (tmo != NULL)
(void) untimeout(tmo);
if (authsock != NULL)
smb_authsock_close(user, authsock);
break;
case SMB_USER_STATE_LOGGED_ON:
user->u_state = SMB_USER_STATE_LOGGING_OFF;
mutex_exit(&user->u_mutex);
smb_session_disconnect_owned_trees(user->u_session, user);
smb_user_auth_logoff(user);
break;
case SMB_USER_STATE_LOGGED_OFF:
case SMB_USER_STATE_LOGGING_OFF:
mutex_exit(&user->u_mutex);
break;
default:
ASSERT(0);
mutex_exit(&user->u_mutex);
break;
}
}
boolean_t
smb_user_hold(smb_user_t *user)
{
SMB_USER_VALID(user);
mutex_enter(&user->u_mutex);
if (user->u_state == SMB_USER_STATE_LOGGED_ON) {
user->u_refcnt++;
mutex_exit(&user->u_mutex);
return (B_TRUE);
}
mutex_exit(&user->u_mutex);
return (B_FALSE);
}
void
smb_user_hold_internal(smb_user_t *user)
{
SMB_USER_VALID(user);
mutex_enter(&user->u_mutex);
user->u_refcnt++;
mutex_exit(&user->u_mutex);
}
void
smb_user_release(
smb_user_t *user)
{
smb_session_t *ssn = user->u_session;
SMB_USER_VALID(user);
smb_llist_flush(&ssn->s_tree_list);
mutex_enter(&user->u_mutex);
ASSERT(user->u_refcnt);
user->u_refcnt--;
switch (user->u_state) {
case SMB_USER_STATE_LOGGING_OFF:
if (user->u_refcnt == 0) {
smb_session_t *ssn = user->u_session;
user->u_state = SMB_USER_STATE_LOGGED_OFF;
smb_llist_post(&ssn->s_user_list, user,
smb_user_delete);
}
break;
case SMB_USER_STATE_LOGGING_ON:
case SMB_USER_STATE_LOGGED_ON:
break;
case SMB_USER_STATE_LOGGED_OFF:
default:
ASSERT(0);
break;
}
mutex_exit(&user->u_mutex);
}
void
smb_user_auth_tmo(void *arg)
{
smb_user_t *user = arg;
smb_request_t *sr;
taskqid_t tqid;
SMB_USER_VALID(user);
sr = smb_request_alloc(user->u_session, 0);
if (sr == NULL)
return;
mutex_enter(&user->u_mutex);
if (user->u_state != SMB_USER_STATE_LOGGING_ON) {
mutex_exit(&user->u_mutex);
smb_request_free(sr);
return;
}
user->u_refcnt++;
mutex_exit(&user->u_mutex);
sr->uid_user = user;
sr->user_cr = user->u_cred;
sr->sr_state = SMB_REQ_STATE_SUBMITTED;
tqid = taskq_dispatch(
user->u_server->sv_worker_pool,
smb_user_logoff_tq, sr, TQ_SLEEP);
VERIFY(tqid != TASKQID_INVALID);
}
static void
smb_user_logoff_tq(void *arg)
{
smb_request_t *sr = arg;
SMB_REQ_VALID(sr);
mutex_enter(&sr->sr_mutex);
sr->sr_worker = curthread;
sr->sr_state = SMB_REQ_STATE_ACTIVE;
mutex_exit(&sr->sr_mutex);
smb_user_logoff(sr->uid_user);
sr->sr_state = SMB_REQ_STATE_COMPLETED;
smb_request_free(sr);
}
boolean_t
smb_user_is_admin(smb_user_t *user)
{
#ifdef _KERNEL
char sidstr[SMB_SID_STRSZ];
ksidlist_t *ksidlist;
ksid_t *ksid;
uint32_t rid;
int ret;
#endif
boolean_t rc = B_FALSE;
ASSERT(user);
ASSERT(user->u_cred);
if (SMB_USER_IS_ADMIN(user))
return (B_TRUE);
#ifdef _KERNEL
(void) strlcpy(sidstr, ADMINISTRATORS_SID, SMB_SID_STRSZ);
ret = smb_sid_splitstr(sidstr, &rid);
ASSERT3S(ret, ==, 0);
ksidlist = crgetsidlist(user->u_cred);
ASSERT(ksidlist);
ksid = crgetsid(user->u_cred, KSID_USER);
ASSERT(ksid != NULL);
ASSERT(ksid->ks_domain != NULL);
ASSERT(ksid->ks_domain->kd_name != NULL);
if ((rid == ksid->ks_rid &&
strcmp(sidstr, ksid_getdomain(ksid)) == 0) ||
ksidlist_has_sid(ksidlist, sidstr, rid)) {
user->u_flags |= SMB_USER_FLAG_ADMIN;
rc = B_TRUE;
}
#endif
return (rc);
}
boolean_t
smb_user_namecmp(smb_user_t *user, const char *name)
{
char *fq_name;
boolean_t match;
if (smb_strcasecmp(name, user->u_name, 0) == 0)
return (B_TRUE);
fq_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
(void) snprintf(fq_name, MAXNAMELEN, "%s\\%s",
user->u_domain, user->u_name);
match = (smb_strcasecmp(name, fq_name, 0) == 0);
if (!match) {
(void) snprintf(fq_name, MAXNAMELEN, "%s@%s",
user->u_name, user->u_domain);
match = (smb_strcasecmp(name, fq_name, 0) == 0);
}
kmem_free(fq_name, MAXNAMELEN);
return (match);
}
int
smb_user_enum(smb_user_t *user, smb_svcenum_t *svcenum)
{
int rc = 0;
ASSERT(user);
ASSERT(user->u_magic == SMB_USER_MAGIC);
if (svcenum->se_type == SMB_SVCENUM_TYPE_USER)
return (smb_user_enum_private(user, svcenum));
return (rc);
}
void
smb_user_inc_trees(smb_user_t *user)
{
mutex_enter(&user->u_mutex);
user->u_owned_tree_cnt++;
mutex_exit(&user->u_mutex);
}
void
smb_user_dec_trees(smb_user_t *user)
{
mutex_enter(&user->u_mutex);
user->u_owned_tree_cnt--;
if (user->u_owned_tree_cnt == 0)
cv_broadcast(&user->u_owned_tree_cv);
mutex_exit(&user->u_mutex);
}
int smb_user_wait_tree_tmo = 30;
void
smb_user_wait_trees(smb_user_t *user)
{
clock_t time;
time = SEC_TO_TICK(smb_user_wait_tree_tmo) + ddi_get_lbolt();
mutex_enter(&user->u_mutex);
while (user->u_owned_tree_cnt != 0) {
if (cv_timedwait(&user->u_owned_tree_cv,
&user->u_mutex, time) < 0)
break;
}
mutex_exit(&user->u_mutex);
if (user->u_owned_tree_cnt != 0) {
#ifdef DEBUG
cmn_err(CE_NOTE, "!smb_user_wait_trees failed");
#endif
DTRACE_PROBE1(max__wait, smb_user_t *, user);
}
}
static void
smb_user_delete(void *arg)
{
smb_session_t *session;
smb_user_t *user = (smb_user_t *)arg;
uint32_t ucount;
SMB_USER_VALID(user);
ASSERT(user->u_refcnt == 0);
ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
ASSERT(user->u_authsock == NULL);
ASSERT(user->u_auth_tmo == NULL);
session = user->u_session;
smb_server_dec_users(session->s_server);
smb_llist_enter(&session->s_user_list, RW_WRITER);
smb_llist_remove(&session->s_user_list, user);
smb_idpool_free(&session->s_uid_pool, user->u_uid);
ucount = smb_llist_get_count(&session->s_user_list);
smb_llist_exit(&session->s_user_list);
if (ucount == 0) {
smb_rwx_rwenter(&session->s_lock, RW_WRITER);
if (session->s_state == SMB_SESSION_STATE_NEGOTIATED &&
session->s_auth_tmo == NULL) {
session->s_auth_tmo =
timeout((tmo_func_t)smb_session_disconnect,
session, SEC_TO_TICK(smb_session_auth_tmo));
}
smb_rwx_cvbcast(&session->s_lock);
smb_rwx_rwexit(&session->s_lock);
}
mutex_enter(&user->u_mutex);
mutex_exit(&user->u_mutex);
user->u_magic = (uint32_t)~SMB_USER_MAGIC;
mutex_destroy(&user->u_mutex);
if (user->u_cred)
crfree(user->u_cred);
if (user->u_privcred)
crfree(user->u_privcred);
smb_mem_free(user->u_name);
smb_mem_free(user->u_domain);
kmem_cache_free(smb_cache_user, user);
}
cred_t *
smb_user_getcred(smb_user_t *user)
{
return (user->u_cred);
}
cred_t *
smb_user_getprivcred(smb_user_t *user)
{
return ((user->u_privcred)? user->u_privcred : user->u_cred);
}
#ifdef _KERNEL
void
smb_user_setcred(smb_user_t *user, cred_t *cr, uint32_t privileges)
{
cred_t *privcred = NULL;
ASSERT(cr);
crhold(cr);
if ((privileges & SMB_USER_PRIV_CHANGE_NOTIFY) != 0) {
(void) crsetpriv(cr, PRIV_FILE_DAC_SEARCH, NULL);
}
if ((privileges & SMB_USER_PRIV_TAKE_OWNERSHIP) != 0) {
(void) crsetpriv(cr,
PRIV_FILE_CHOWN,
PRIV_FILE_CHOWN_SELF,
NULL);
}
if ((privileges & SMB_USER_PRIV_READ_FILE) != 0) {
(void) crsetpriv(cr, PRIV_FILE_DAC_READ, NULL);
}
if ((privileges & SMB_USER_PRIV_WRITE_FILE) != 0) {
(void) crsetpriv(cr,
PRIV_FILE_DAC_WRITE,
PRIV_FILE_OWNER,
NULL);
}
if (privileges & (SMB_USER_PRIV_BACKUP | SMB_USER_PRIV_RESTORE))
privcred = crdup(cr);
if (privcred != NULL) {
if (privileges & SMB_USER_PRIV_BACKUP) {
(void) crsetpriv(privcred, PRIV_FILE_DAC_READ,
PRIV_FILE_DAC_SEARCH, PRIV_SYS_MOUNT, NULL);
}
if (privileges & SMB_USER_PRIV_RESTORE) {
(void) crsetpriv(privcred, PRIV_FILE_DAC_WRITE,
PRIV_FILE_CHOWN, PRIV_FILE_CHOWN_SELF,
PRIV_FILE_DAC_SEARCH, PRIV_FILE_LINK_ANY,
PRIV_FILE_OWNER, PRIV_FILE_SETID,
PRIV_SYS_LINKDIR, PRIV_SYS_MOUNT, NULL);
}
}
user->u_cred = cr;
user->u_privcred = privcred;
user->u_privileges = privileges;
}
#endif
boolean_t
smb_user_has_security_priv(smb_user_t *user, cred_t *cr)
{
if ((user->u_privileges & SMB_USER_PRIV_SECURITY) != 0)
return (B_TRUE);
#ifdef _KERNEL
if (PRIV_POLICY_ONLY(cr,
priv_getbyname(PRIV_FILE_DAC_READ, 0), B_FALSE) ||
PRIV_POLICY_ONLY(cr,
priv_getbyname(PRIV_FILE_DAC_WRITE, 0), B_FALSE))
return (B_TRUE);
#else
if ((user->u_privileges &
(SMB_USER_PRIV_BACKUP |
SMB_USER_PRIV_RESTORE |
SMB_USER_PRIV_READ_FILE |
SMB_USER_PRIV_WRITE_FILE)) != 0)
return (B_TRUE);
#endif
return (B_FALSE);
}
static int
smb_user_enum_private(smb_user_t *user, smb_svcenum_t *svcenum)
{
uint8_t *pb;
uint_t nbytes;
int rc;
if (svcenum->se_nskip > 0) {
svcenum->se_nskip--;
return (0);
}
if (svcenum->se_nitems >= svcenum->se_nlimit) {
svcenum->se_nitems = svcenum->se_nlimit;
return (0);
}
pb = &svcenum->se_buf[svcenum->se_bused];
rc = smb_user_netinfo_encode(user, pb, svcenum->se_bavail, &nbytes);
if (rc == 0) {
svcenum->se_bavail -= nbytes;
svcenum->se_bused += nbytes;
svcenum->se_nitems++;
}
return (rc);
}
int
smb_user_netinfo_encode(smb_user_t *user, uint8_t *buf, size_t buflen,
uint32_t *nbytes)
{
smb_netuserinfo_t info;
int rc;
smb_user_netinfo_init(user, &info);
rc = smb_netuserinfo_encode(&info, buf, buflen, nbytes);
smb_user_netinfo_fini(&info);
return (rc);
}
void
smb_user_netinfo_init(smb_user_t *user, smb_netuserinfo_t *info)
{
smb_session_t *session;
char *buf;
ASSERT(user);
ASSERT(user->u_domain);
ASSERT(user->u_name);
session = user->u_session;
ASSERT(session);
ASSERT(session->workstation);
info->ui_session_id = session->s_kid;
info->ui_user_id = user->u_ssnid;
info->ui_native_os = session->native_os;
info->ui_ipaddr = session->ipaddr;
info->ui_numopens = session->s_file_cnt;
info->ui_logon_time = user->u_logon_time;
info->ui_flags = user->u_flags;
info->ui_posix_uid = crgetuid(user->u_cred);
info->ui_domain_len = user->u_domain_len;
info->ui_domain = smb_mem_strdup(user->u_domain);
info->ui_account_len = user->u_name_len;
info->ui_account = smb_mem_strdup(user->u_name);
buf = kmem_alloc(MAXNAMELEN, KM_SLEEP);
smb_session_getclient(session, buf, MAXNAMELEN);
info->ui_workstation_len = strlen(buf) + 1;
info->ui_workstation = smb_mem_strdup(buf);
kmem_free(buf, MAXNAMELEN);
}
void
smb_user_netinfo_fini(smb_netuserinfo_t *info)
{
if (info == NULL)
return;
if (info->ui_domain)
smb_mem_free(info->ui_domain);
if (info->ui_account)
smb_mem_free(info->ui_account);
if (info->ui_workstation)
smb_mem_free(info->ui_workstation);
bzero(info, sizeof (smb_netuserinfo_t));
}
uint64_t smb_user_auth_logoff_failures;
static void
smb_user_auth_logoff(smb_user_t *user)
{
smb_server_t *sv = user->u_server;
uint32_t audit_sid;
if (sv->sv_state != SMB_SERVER_STATE_RUNNING)
return;
if (smb_threshold_enter(&sv->sv_logoff_ct) != 0) {
smb_user_auth_logoff_failures++;
return;
}
audit_sid = user->u_audit_sid;
(void) smb_kdoor_upcall(sv, SMB_DR_USER_AUTH_LOGOFF,
&audit_sid, xdr_uint32_t, NULL, NULL);
smb_threshold_exit(&sv->sv_logoff_ct);
}
boolean_t
smb_is_same_user(cred_t *cr1, cred_t *cr2)
{
ksid_t *ks1 = crgetsid(cr1, KSID_USER);
ksid_t *ks2 = crgetsid(cr2, KSID_USER);
if (ks1 == NULL || ks2 == NULL) {
return (B_FALSE);
}
return (ks1->ks_rid == ks2->ks_rid &&
strcmp(ks1->ks_domain->kd_name, ks2->ks_domain->kd_name) == 0);
}