#include <sys/types.h>
#include <sys/sid.h>
#include <sys/priv_names.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <smbsrv/smb_idmap.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_token.h>
#include <smbsrv/smb2_kproto.h>
static uint32_t smb_authsock_open(smb_request_t *);
static int smb_authsock_send(ksocket_t, void *, size_t);
static int smb_authsock_recv(ksocket_t, void *, size_t);
static uint32_t smb_authsock_sendrecv(smb_request_t *, smb_lsa_msg_hdr_t *hdr,
void *sndbuf, void **recvbuf);
static uint32_t smb_auth_do_clinfo(smb_request_t *);
static uint32_t smb_auth_do_oldreq(smb_request_t *);
static uint32_t smb_auth_get_token(smb_request_t *);
static uint32_t smb_priv_xlate(smb_token_t *);
int
smb_authenticate_old(smb_request_t *sr)
{
smb_user_t *user = NULL;
uint32_t status;
user = smb_user_new(sr->session);
if (user == NULL)
return (NT_STATUS_TOO_MANY_SESSIONS);
sr->uid_user = user;
sr->smb_uid = user->u_uid;
sr->smb2_ssnid = 0;
if ((status = smb_authsock_open(sr)) != 0)
goto errout;
if ((status = smb_auth_do_clinfo(sr)) != 0)
goto errout;
if ((status = smb_auth_do_oldreq(sr)) != 0)
goto errout;
if ((status = smb_auth_get_token(sr)) != 0)
goto errout;
return (0);
errout:
smb_user_logoff(user);
return (status);
}
static uint32_t
smb_auth_do_oldreq(smb_request_t *sr)
{
smb_lsa_msg_hdr_t msg_hdr;
smb_logon_t user_info;
XDR xdrs;
smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup;
void *sbuf = NULL;
void *rbuf = NULL;
uint32_t slen = 0;
uint32_t rlen = 0;
uint32_t status;
bool_t ok;
bzero(&user_info, sizeof (smb_logon_t));
user_info.lg_level = NETR_NETWORK_LOGON;
user_info.lg_username = sinfo->ssi_user;
user_info.lg_domain = sinfo->ssi_domain;
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_ntpwd;
user_info.lg_nt_password.len = sinfo->ssi_ntpwlen;
user_info.lg_lm_password.val = sinfo->ssi_lmpwd;
user_info.lg_lm_password.len = sinfo->ssi_lmpwlen;
user_info.lg_native_os = sr->session->native_os;
user_info.lg_native_lm = sr->session->native_lm;
slen = xdr_sizeof(smb_logon_xdr, &user_info);
sbuf = kmem_alloc(slen, KM_SLEEP);
xdrmem_create(&xdrs, sbuf, slen, XDR_ENCODE);
ok = smb_logon_xdr(&xdrs, &user_info);
xdr_destroy(&xdrs);
if (!ok) {
status = RPC_NT_BAD_STUB_DATA;
goto out;
}
msg_hdr.lmh_msgtype = LSA_MTYPE_OLDREQ;
msg_hdr.lmh_msglen = slen;
status = smb_authsock_sendrecv(sr, &msg_hdr, sbuf, &rbuf);
if (status != 0)
goto out;
rlen = msg_hdr.lmh_msglen;
kmem_free(sbuf, slen);
sbuf = NULL;
switch (msg_hdr.lmh_msgtype) {
case LSA_MTYPE_OK:
status = 0;
break;
case LSA_MTYPE_ERROR:
if (rlen == sizeof (smb_lsa_eresp_t)) {
smb_lsa_eresp_t *ler = rbuf;
status = ler->ler_ntstatus;
break;
}
default:
status = NT_STATUS_INTERNAL_ERROR;
break;
}
out:
if (rbuf != NULL)
kmem_free(rbuf, rlen);
if (sbuf != NULL)
kmem_free(sbuf, slen);
return (status);
}
int
smb_authenticate_ext(smb_request_t *sr)
{
smb_lsa_msg_hdr_t msg_hdr;
smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup;
smb_user_t *user = NULL;
void *rbuf = NULL;
uint32_t rlen = 0;
uint32_t status;
ASSERT(sr->uid_user == NULL);
if (sr->session->dialect >= SMB_VERS_2_BASE) {
ASSERT(sr->smb_uid == 0);
sr->smb_uid = 0;
} else {
ASSERT(sr->smb2_ssnid == 0);
sr->smb2_ssnid = 0;
}
if (sr->smb2_ssnid == 0 && sr->smb_uid == 0) {
user = smb_user_new(sr->session);
if (user == NULL)
return (NT_STATUS_TOO_MANY_SESSIONS);
sr->uid_user = user;
if (sr->session->dialect >= SMB_VERS_2_BASE) {
sr->smb2_ssnid = user->u_ssnid;
} else {
sr->smb_uid = user->u_uid;
}
if ((status = smb_authsock_open(sr)) != 0)
goto errout;
if ((status = smb_auth_do_clinfo(sr)) != 0)
goto errout;
msg_hdr.lmh_msgtype = LSA_MTYPE_ESFIRST;
if (sr->session->dialect >= SMB_VERS_3_11) {
if (smb31_preauth_sha512_calc(sr, &sr->command,
sr->session->smb31_preauth_hashval,
user->u_preauth_hashval) != 0)
cmn_err(CE_WARN, "(2) Preauth hash calculation "
"failed");
}
} else {
user = smb_session_lookup_uid_st(sr->session,
sr->smb2_ssnid, sr->smb_uid, SMB_USER_STATE_LOGGING_ON);
if (user == NULL)
return (NT_STATUS_USER_SESSION_DELETED);
sr->uid_user = user;
msg_hdr.lmh_msgtype = LSA_MTYPE_ESNEXT;
if (sr->session->dialect >= SMB_VERS_3_11) {
if (smb31_preauth_sha512_calc(sr, &sr->command,
user->u_preauth_hashval,
user->u_preauth_hashval) != 0)
cmn_err(CE_WARN, "(4) Preauth hash calculation "
"failed");
}
}
msg_hdr.lmh_msglen = sinfo->ssi_iseclen;
status = smb_authsock_sendrecv(sr, &msg_hdr,
sinfo->ssi_isecblob, &rbuf);
if (status != 0)
goto errout;
rlen = msg_hdr.lmh_msglen;
switch (msg_hdr.lmh_msgtype) {
case LSA_MTYPE_ES_CONT:
sinfo->ssi_oseclen = (uint16_t)rlen;
sinfo->ssi_osecblob = smb_srm_alloc(sr, sinfo->ssi_oseclen);
bcopy(rbuf, sinfo->ssi_osecblob, sinfo->ssi_oseclen);
status = NT_STATUS_MORE_PROCESSING_REQUIRED;
break;
case LSA_MTYPE_ES_DONE:
sinfo->ssi_oseclen = (uint16_t)rlen;
sinfo->ssi_osecblob = smb_srm_alloc(sr, sinfo->ssi_oseclen);
bcopy(rbuf, sinfo->ssi_osecblob, sinfo->ssi_oseclen);
sinfo->ssi_ntpwlen = 0;
status = smb_auth_get_token(sr);
break;
case LSA_MTYPE_ERROR:
if (rlen == sizeof (smb_lsa_eresp_t)) {
smb_lsa_eresp_t *ler = rbuf;
status = ler->ler_ntstatus;
goto errout;
}
default:
status = NT_STATUS_INTERNAL_ERROR;
goto errout;
}
if (status != 0 && status != NT_STATUS_MORE_PROCESSING_REQUIRED) {
errout:
smb_user_logoff(user);
}
if (rbuf != NULL)
kmem_free(rbuf, rlen);
return (status);
}
static uint32_t
smb_auth_do_clinfo(smb_request_t *sr)
{
smb_lsa_msg_hdr_t msg_hdr;
smb_lsa_clinfo_t clinfo;
void *rbuf = NULL;
uint32_t status;
msg_hdr.lmh_msgtype = LSA_MTYPE_CLINFO;
msg_hdr.lmh_msglen = sizeof (clinfo);
clinfo.lci_clnt_ipaddr = sr->session->ipaddr;
(void) memcpy(clinfo.lci_challenge_key,
sr->session->challenge_key,
sizeof (clinfo.lci_challenge_key));
status = smb_authsock_sendrecv(sr, &msg_hdr, &clinfo, &rbuf);
if (rbuf != NULL) {
kmem_free(rbuf, msg_hdr.lmh_msglen);
rbuf = NULL;
}
return (status);
}
static uint32_t
smb_auth_get_token(smb_request_t *sr)
{
smb_lsa_msg_hdr_t msg_hdr;
XDR xdrs;
smb_user_t *user = sr->uid_user;
smb_token_t *token = NULL;
cred_t *cr = NULL;
void *rbuf = NULL;
uint32_t rlen = 0;
uint32_t privileges;
uint32_t status;
bool_t ok;
msg_hdr.lmh_msgtype = LSA_MTYPE_GETTOK;
msg_hdr.lmh_msglen = 0;
status = smb_authsock_sendrecv(sr, &msg_hdr, NULL, &rbuf);
if (status != 0)
goto errout;
rlen = msg_hdr.lmh_msglen;
switch (msg_hdr.lmh_msgtype) {
case LSA_MTYPE_TOKEN:
status = 0;
break;
case LSA_MTYPE_ERROR:
if (rlen == sizeof (smb_lsa_eresp_t)) {
smb_lsa_eresp_t *ler = rbuf;
status = ler->ler_ntstatus;
goto errout;
}
default:
status = NT_STATUS_INTERNAL_ERROR;
goto errout;
}
xdrmem_create(&xdrs, rbuf, rlen, XDR_DECODE);
token = kmem_zalloc(sizeof (smb_token_t), KM_SLEEP);
ok = smb_token_xdr(&xdrs, token);
xdr_destroy(&xdrs);
if (!ok) {
status = RPC_NT_BAD_STUB_DATA;
goto errout;
}
kmem_free(rbuf, rlen);
rbuf = NULL;
cr = smb_cred_create(token);
if (cr == NULL) {
status = NT_STATUS_INTERNAL_ERROR;
goto errout;
}
privileges = smb_priv_xlate(token);
(void) smb_user_logon(user, cr,
token->tkn_domain_name, token->tkn_account_name,
token->tkn_flags, privileges, token->tkn_audit_sid);
crfree(cr);
if (sr->session->dialect >= SMB_VERS_3_0)
smb3_encrypt_begin(sr->uid_user, token);
if ((token->tkn_flags & (SMB_ATF_GUEST | SMB_ATF_ANON)) == 0) {
if (sr->session->dialect >= SMB_VERS_2_BASE) {
smb2_sign_begin(sr, token);
} else {
smb_sign_begin(sr, token);
}
}
smb_token_free(token);
sr->user_cr = user->u_cred;
return (0);
errout:
if (rbuf != NULL)
kmem_free(rbuf, rlen);
if (token != NULL)
smb_token_free(token);
return (status);
}
void
smb_token_free(smb_token_t *token)
{
if (token != NULL) {
xdr_free(smb_token_xdr, (char *)token);
kmem_free(token, sizeof (smb_token_t));
}
}
static uint32_t
smb_priv_xlate(smb_token_t *token)
{
uint32_t privileges = 0;
if (smb_token_query_privilege(token, SE_SECURITY_LUID))
privileges |= SMB_USER_PRIV_SECURITY;
if (smb_token_query_privilege(token, SE_TAKE_OWNERSHIP_LUID))
privileges |= SMB_USER_PRIV_TAKE_OWNERSHIP;
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_CHANGE_NOTIFY_LUID))
privileges |= SMB_USER_PRIV_CHANGE_NOTIFY;
if (smb_token_query_privilege(token, SE_READ_FILE_LUID))
privileges |= SMB_USER_PRIV_READ_FILE;
if (smb_token_query_privilege(token, SE_WRITE_FILE_LUID))
privileges |= SMB_USER_PRIV_WRITE_FILE;
return (privileges);
}
static void
smb_authsock_cancel(smb_request_t *sr)
{
smb_user_t *user = sr->cancel_arg2;
ksocket_t authsock = NULL;
ASSERT(user == sr->uid_user);
mutex_enter(&user->u_mutex);
if (user->u_state == SMB_USER_STATE_LOGGING_ON) {
if ((authsock = user->u_authsock) != NULL)
ksocket_hold(authsock);
}
mutex_exit(&user->u_mutex);
if (authsock != NULL) {
(void) ksocket_shutdown(authsock, SHUT_RDWR, sr->user_cr);
ksocket_rele(authsock);
}
}
static uint32_t
smb_authsock_sendrecv(smb_request_t *sr, smb_lsa_msg_hdr_t *hdr,
void *sndbuf, void **recvbuf)
{
smb_user_t *user = sr->uid_user;
ksocket_t so;
uint32_t status;
int rc;
mutex_enter(&user->u_mutex);
so = user->u_authsock;
if (so == NULL) {
mutex_exit(&user->u_mutex);
return (NT_STATUS_INTERNAL_ERROR);
}
ksocket_hold(so);
mutex_exit(&user->u_mutex);
mutex_enter(&sr->sr_mutex);
if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
mutex_exit(&sr->sr_mutex);
status = NT_STATUS_CANCELLED;
goto out;
}
sr->sr_state = SMB_REQ_STATE_WAITING_AUTH;
sr->cancel_method = smb_authsock_cancel;
sr->cancel_arg2 = user;
mutex_exit(&sr->sr_mutex);
rc = smb_authsock_send(so, hdr, sizeof (*hdr));
if (rc == 0 && hdr->lmh_msglen != 0) {
rc = smb_authsock_send(so, sndbuf, hdr->lmh_msglen);
}
if (rc == 0)
rc = smb_authsock_recv(so, hdr, sizeof (*hdr));
if (rc == 0 && hdr->lmh_msglen != 0) {
*recvbuf = kmem_alloc(hdr->lmh_msglen, KM_SLEEP);
rc = smb_authsock_recv(so, *recvbuf, hdr->lmh_msglen);
}
switch (rc) {
case 0:
status = 0;
break;
case EIO:
status = RPC_NT_COMM_FAILURE;
break;
case ENOTCONN:
status = RPC_NT_PIPE_CLOSED;
break;
default:
status = RPC_NT_CALL_FAILED;
break;
}
mutex_enter(&sr->sr_mutex);
switch_state:
switch (sr->sr_state) {
case SMB_REQ_STATE_WAITING_AUTH:
sr->sr_state = SMB_REQ_STATE_ACTIVE;
break;
case SMB_REQ_STATE_CANCEL_PENDING:
cv_wait(&sr->sr_st_cv, &sr->sr_mutex);
goto switch_state;
case SMB_REQ_STATE_CANCELLED:
status = NT_STATUS_CANCELLED;
break;
default:
status = NT_STATUS_INTERNAL_ERROR;
break;
}
sr->cancel_method = NULL;
sr->cancel_arg2 = NULL;
mutex_exit(&sr->sr_mutex);
out:
ksocket_rele(so);
if (status != 0 && *recvbuf != NULL) {
kmem_free(*recvbuf, hdr->lmh_msglen);
*recvbuf = NULL;
}
return (status);
}
static struct sockaddr_un smbauth_sockname = {
AF_UNIX, SMB_AUTHSVC_SOCKNAME };
struct timeval smb_auth_recv_tmo = { 45, 0 };
struct timeval smb_auth_send_tmo = { 15, 0 };
int smb_auth_total_tmo = 45;
static uint32_t
smb_authsock_open(smb_request_t *sr)
{
smb_user_t *user = sr->uid_user;
smb_server_t *sv = sr->sr_server;
ksocket_t so = NULL;
uint32_t status = 0;
int rc;
if ((rc = smb_threshold_enter(&sv->sv_ssetup_ct)) != 0)
return (NT_STATUS_NO_LOGON_SERVERS);
rc = ksocket_socket(&so, AF_UNIX, SOCK_STREAM, 0,
KSOCKET_SLEEP, CRED());
if (rc != 0) {
cmn_err(CE_NOTE, "smb_authsock_open: socket, rc=%d", rc);
smb_threshold_exit(&sv->sv_ssetup_ct);
status = NT_STATUS_INSUFF_SERVER_RESOURCES;
goto errout;
}
mutex_enter(&user->u_mutex);
if (user->u_authsock != NULL) {
mutex_exit(&user->u_mutex);
smb_authsock_close(user, so);
status = NT_STATUS_INTERNAL_ERROR;
goto errout;
}
user->u_authsock = so;
if (smb_auth_total_tmo != 0) {
user->u_auth_tmo = timeout(smb_user_auth_tmo, user,
SEC_TO_TICK(smb_auth_total_tmo));
}
mutex_exit(&user->u_mutex);
(void) ksocket_setsockopt(so, SOL_SOCKET, SO_SNDTIMEO,
&smb_auth_send_tmo, sizeof (smb_auth_send_tmo), CRED());
(void) ksocket_setsockopt(so, SOL_SOCKET, SO_RCVTIMEO,
&smb_auth_recv_tmo, sizeof (smb_auth_recv_tmo), CRED());
mutex_enter(&sr->sr_mutex);
if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
mutex_exit(&sr->sr_mutex);
status = NT_STATUS_CANCELLED;
goto errout;
}
sr->sr_state = SMB_REQ_STATE_WAITING_AUTH;
sr->cancel_method = smb_authsock_cancel;
sr->cancel_arg2 = user;
mutex_exit(&sr->sr_mutex);
rc = ksocket_connect(so, (struct sockaddr *)&smbauth_sockname,
sizeof (smbauth_sockname), CRED());
if (rc != 0) {
DTRACE_PROBE1(error, int, rc);
status = NT_STATUS_NETLOGON_NOT_STARTED;
}
mutex_enter(&sr->sr_mutex);
switch_state:
switch (sr->sr_state) {
case SMB_REQ_STATE_WAITING_AUTH:
sr->sr_state = SMB_REQ_STATE_ACTIVE;
break;
case SMB_REQ_STATE_CANCEL_PENDING:
cv_wait(&sr->sr_st_cv, &sr->sr_mutex);
goto switch_state;
case SMB_REQ_STATE_CANCELLED:
status = NT_STATUS_CANCELLED;
break;
default:
status = NT_STATUS_INTERNAL_ERROR;
break;
}
sr->cancel_method = NULL;
sr->cancel_arg2 = NULL;
mutex_exit(&sr->sr_mutex);
errout:
return (status);
}
static int
smb_authsock_send(ksocket_t so, void *buf, size_t len)
{
int rc;
size_t iocnt = 0;
rc = ksocket_send(so, buf, len, 0, &iocnt, CRED());
if (rc == 0 && iocnt != len) {
DTRACE_PROBE1(short, size_t, iocnt);
rc = EIO;
}
if (rc != 0) {
DTRACE_PROBE1(error, int, rc);
}
return (rc);
}
static int
smb_authsock_recv(ksocket_t so, void *buf, size_t len)
{
int rc;
size_t iocnt = 0;
rc = ksocket_recv(so, buf, len, MSG_WAITALL, &iocnt, CRED());
if (rc == 0) {
if (iocnt == 0) {
DTRACE_PROBE1(discon, struct sonode *, so);
rc = ENOTCONN;
} else if (iocnt != len) {
DTRACE_PROBE1(short, size_t, iocnt);
rc = EIO;
}
}
if (rc != 0) {
DTRACE_PROBE1(error, int, rc);
}
return (rc);
}
void
smb_authsock_close(smb_user_t *user, ksocket_t so)
{
(void) ksocket_shutdown(so, SHUT_RDWR, CRED());
(void) ksocket_close(so, CRED());
smb_threshold_exit(&user->u_server->sv_ssetup_ct);
}