smbrdr_lib.c revision 148c5f43199ca0b43fc8e3b643aab11cd66ea327
/*
* 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.
*/
/*
* This file provides some common functionality for SMB Redirector
* module.
*/
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <smbrdr.h>
smb_log_hdl_t smbrdr_log_hdl;
static DWORD smbrdr_handle_setup(smbrdr_handle_t *, unsigned char,
struct sdb_session *, struct sdb_logon *, struct sdb_netuse *);
static int smbrdr_hdr_setup(smbrdr_handle_t *);
static DWORD smbrdr_hdr_process(smbrdr_handle_t *, smb_hdr_t *);
static int smbrdr_sign(smb_sign_ctx_t *, smb_msgbuf_t *);
static int smbrdr_sign_chk(smb_sign_ctx_t *, smb_msgbuf_t *, unsigned char *);
void smbrdr_lock_transport() { nb_lock(); }
void smbrdr_unlock_transport() { nb_unlock(); }
#pragma init(_smbrdr_init)
void
_smbrdr_init(void)
{
smbrdr_log_hdl = smb_log_create(SMBRDR_LOG_MAXCNT, SMBRDR_LOG_NAME);
}
/*
* smbrdr_request_init
*
* Setup a handle with given information and then
* setup a SMB header structure.
*
* Returns:
*
* NT_STATUS_NO_MEMORY no memory for creating request
* NT_STATUS_INTERNAL_ERROR header encode failed or crypto failed
* NT_STATUS_SUCCESS successful
*/
DWORD
smbrdr_request_init(smbrdr_handle_t *srh,
unsigned char cmd,
struct sdb_session *session,
struct sdb_logon *logon,
struct sdb_netuse *netuse)
{
DWORD status;
status = smbrdr_handle_setup(srh, cmd, session, logon, netuse);
if (status != NT_STATUS_SUCCESS)
return (status);
if (smbrdr_hdr_setup(srh) < SMB_HEADER_LEN) {
smbrdr_handle_free(srh);
return (NT_STATUS_INTERNAL_ERROR);
}
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_send
*
* Send the SMB packet pointed by the given handle over
* network.
*
* Returns:
*
* NT_STATUS_INTERNAL_ERROR crypto framework failure
* NT_STATUS_UNEXPECTED_NETWORK_ERROR send failed
* NT_STATUS_SUCCESS successful
*/
DWORD
smbrdr_send(smbrdr_handle_t *srh)
{
int rc;
if (smbrdr_sign(&srh->srh_session->sign_ctx, &srh->srh_mbuf) !=
SMBAUTH_SUCCESS) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_send[%d]: signing failed", srh->srh_cmd);
return (NT_STATUS_INTERNAL_ERROR);
}
rc = nb_send(srh->srh_session->sock, srh->srh_buf,
smb_msgbuf_used(&srh->srh_mbuf));
if (rc < 0) {
/*
* Make the sequence number of the next SMB request even
* to avoid DC from failing the next SMB request with
* ACCESS_DENIED.
*/
smb_mac_dec_seqnum(&srh->srh_session->sign_ctx);
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_send[%d]: send failed (%d)", srh->srh_cmd, rc);
return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
}
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_rcv
*
* Receive a SMB response and decode the packet header.
*
* "Implementing CIFS" book, SMB requests always have an even sequence
* number and replies always have an odd.
*
* With the original code, if the SMB Redirector skip the counter increment
* in the event of any failure during SmbSessionSetupAndX, it causes the
* domain controller to fail the next SMB request(odd sequence number)
* with ACCESS_DENIED.
*
* Smbrdr module should use the same sequence number (i.e. ssc_seqnum of the
* SMB Sign context) for generating the MAC signature for all incoming
* responses per SmbTransact request. Otherwise, the validation will fail.
* It is now fixed by decrementing the sequence number prior to validating
* the subsequent responses for a single request.
*
* Returns:
*
* status code returned by smbrdr_hdr_process()
* NT_STATUS_UNEXPECTED_NETWORK_ERROR receive failed
* NT_STATUS_SUCCESS successful
*/
DWORD
smbrdr_rcv(smbrdr_handle_t *srh, int is_first_rsp)
{
smb_hdr_t smb_hdr;
DWORD status;
int rc;
smb_sign_ctx_t *sign_ctx = &srh->srh_session->sign_ctx;
rc = nb_rcv(srh->srh_session->sock, srh->srh_buf, SMBRDR_REQ_BUFSZ, 0);
if (rc < 0) {
smb_mac_inc_seqnum(sign_ctx);
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_rcv[%d]: receive failed (%d)", srh->srh_cmd, rc);
return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
}
smb_msgbuf_init(&srh->srh_mbuf, srh->srh_buf, rc, srh->srh_mbflags);
status = smbrdr_hdr_process(srh, &smb_hdr);
if (status != NT_STATUS_SUCCESS) {
smb_mac_inc_seqnum(sign_ctx);
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_rcv[%d]: failed (%s)", srh->srh_cmd,
xlate_nt_status(status));
return (status);
}
if (!is_first_rsp)
smb_mac_dec_seqnum(sign_ctx);
if (!smbrdr_sign_chk(sign_ctx,
&srh->srh_mbuf, smb_hdr.extra.extra.security_sig)) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_rcv[%d]: bad signature", srh->srh_cmd);
return (NT_STATUS_INVALID_NETWORK_RESPONSE);
}
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_exchange
*
* Send the SMB packet pointed by the given handle over
* network. Receive the response and decode the packet header.
*
* From "Implementing CIFS" book, SMB requests always have an even sequence
* number and replies always have an odd.
*
* With the original code, if the SMB Redirector skips the counter increment
* in the event of any failure during SmbSessionSetupAndX, it causes the
* domain controller to fail the next SMB request(odd sequence number)
* with ACCESS_DENIED.
*
* Returns:
*
* status code returned by smbrdr_hdr_process()
* NT_STATUS_INTERNAL_ERROR crypto framework failure
* NT_STATUS_UNEXPECTED_NETWORK_ERROR send/receive failed
* NT_STATUS_SUCCESS successful
*/
DWORD
smbrdr_exchange(smbrdr_handle_t *srh, smb_hdr_t *smb_hdr, long timeout)
{
smb_sign_ctx_t *sign_ctx;
smb_msgbuf_t *mb;
DWORD status;
int rc;
smbrdr_lock_transport();
mb = &srh->srh_mbuf;
sign_ctx = &srh->srh_session->sign_ctx;
if (smbrdr_sign(sign_ctx, mb) != SMBAUTH_SUCCESS) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_exchange[%d]: signing failed", srh->srh_cmd);
smbrdr_unlock_transport();
return (NT_STATUS_INTERNAL_ERROR);
}
rc = nb_exchange(srh->srh_session->sock,
srh->srh_buf, smb_msgbuf_used(mb),
srh->srh_buf, SMBRDR_REQ_BUFSZ, timeout);
if (rc < 0) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_exchange[%d]: failed (%d)", srh->srh_cmd, rc);
if (srh->srh_cmd != SMB_COM_ECHO) {
/*
* Since SMB echo is used to check the session
* status then don't destroy the session if it's
* SMB echo.
*/
srh->srh_session->state = SDB_SSTATE_STALE;
}
smb_mac_inc_seqnum(sign_ctx);
smbrdr_unlock_transport();
return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
}
/* initialize for processing response */
smb_msgbuf_init(mb, srh->srh_buf, rc, srh->srh_mbflags);
status = smbrdr_hdr_process(srh, smb_hdr);
if (status != NT_STATUS_SUCCESS) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_exchange[%d]: failed (%s)", srh->srh_cmd,
xlate_nt_status(status));
smb_mac_inc_seqnum(sign_ctx);
smbrdr_unlock_transport();
return (status);
}
/* Signature validation */
if (!smbrdr_sign_chk(sign_ctx, mb, smb_hdr->extra.extra.security_sig)) {
smb_log(smbrdr_log_hdl, LOG_ERR,
"smbrdr_exchange[%d]: bad signature", srh->srh_cmd);
smbrdr_unlock_transport();
return (NT_STATUS_INVALID_NETWORK_RESPONSE);
}
smbrdr_unlock_transport();
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_handle_free
*
* Frees the memories allocated for the given handle.
*/
void
smbrdr_handle_free(smbrdr_handle_t *srh)
{
if (srh) {
smb_msgbuf_term(&srh->srh_mbuf);
free(srh->srh_buf);
}
}
/*
* smbrdr_sign_init
*
* This function is called from SessionSetup and initialize the
* signing context for the session if the connected user isn't
* anonymous. This has to call before smbrdr_request_init()
* because it modifies smb_flags2.
*
* The following description is taken out from the "Implementing CIFS"
* book(pg. 304):
*
* "Once the MAC signing has been initialized within a session, all
* messages are numbered using the same counters and signed using
* the same Session Key. This is true even if additional SESSION
* SETUP ANDX exchanges occur."
*
* The original SMB packet signing implementation calculates a MAC
* key each time the SMB Redirector sends the SmbSessionSetupAndx
* request for any non-anonymous/non-guest user which is not desired
* whenever there is a change in the user session key.
*
* If NTLMv2 authentication is used, the MAC key generated for each
* SessionSetup is unique. Since the domain controller expects the
* signature of all incoming requests are signed by the same MAC key
* (i.e. the one that generated for the first non-anonymous SessionSetup),
* access denied is returned for any subsequent SmbSessionSetupAndX
* request.
*/
int
smbrdr_sign_init(struct sdb_session *session, struct sdb_logon *logon)
{
smb_sign_ctx_t *sign_ctx;
int rc = 0;
sign_ctx = &session->sign_ctx;
if ((sign_ctx->ssc_flags & SMB_SCF_REQUIRED) &&
!(sign_ctx->ssc_flags & SMB_SCF_STARTED) &&
(logon->type != SDB_LOGON_ANONYMOUS)) {
if (smb_mac_init(sign_ctx, &logon->auth) != SMBAUTH_SUCCESS)
return (-1);
sign_ctx->ssc_flags |=
(SMB_SCF_STARTED | SMB_SCF_KEY_ISSET_THIS_LOGON);
session->smb_flags2 |= SMB_FLAGS2_SMB_SECURITY_SIGNATURE;
rc = 1;
}
return (rc);
}
/*
* smbrdr_sign_fini
*
* Invalidate the MAC key if the first non-anonymous/non-guest user logon
* fail.
*/
void
smbrdr_sign_fini(struct sdb_session *session)
{
smb_sign_ctx_t *sign_ctx = &session->sign_ctx;
if (sign_ctx->ssc_flags & SMB_SCF_KEY_ISSET_THIS_LOGON) {
sign_ctx->ssc_flags &= ~SMB_SCF_STARTED;
sign_ctx->ssc_flags &= ~SMB_SCF_KEY_ISSET_THIS_LOGON;
sign_ctx->ssc_seqnum = 0;
}
}
/*
* smbrdr_sign_unset_key
*
* The SMB_SCF_KEY_ISSET_THIS_LOGON should be unset upon the successful
* SmbSessionSetupAndX request for the first non-anonymous/non-guest
* logon.
*/
void
smbrdr_sign_unset_key(struct sdb_session *session)
{
smb_sign_ctx_t *sign_ctx = &session->sign_ctx;
sign_ctx->ssc_flags &= ~SMB_SCF_KEY_ISSET_THIS_LOGON;
}
/*
* smbrdr_handle_setup
*
* Allocates a buffer for sending/receiving a SMB request.
* Initialize a smb_msgbuf structure with the allocated buffer.
* Setup given handle (srh) with the specified information.
*
* Returns:
*
* NT_STATUS_NO_MEMORY not enough memory
* NT_STATUS_SUCCESS successful
*/
static DWORD
smbrdr_handle_setup(smbrdr_handle_t *srh,
unsigned char cmd,
struct sdb_session *session,
struct sdb_logon *logon,
struct sdb_netuse *netuse)
{
srh->srh_buf = (unsigned char *)malloc(SMBRDR_REQ_BUFSZ);
if (srh->srh_buf == NULL)
return (NT_STATUS_NO_MEMORY);
bzero(srh->srh_buf, SMBRDR_REQ_BUFSZ);
srh->srh_mbflags = (session->remote_caps & CAP_UNICODE)
? SMB_MSGBUF_UNICODE : 0;
smb_msgbuf_init(&srh->srh_mbuf, srh->srh_buf,
SMBRDR_REQ_BUFSZ, srh->srh_mbflags);
srh->srh_cmd = cmd;
srh->srh_session = session;
srh->srh_user = logon;
srh->srh_tree = netuse;
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_hdr_setup
*
* Build an SMB header based on the information in the given handle.
* The SMB header is described in section 3.2 of the CIFS spec.
* As this is a canned function, no error checking is performed here.
* The return value from smb_msgbuf_encode is simply returned to the caller.
*/
static int
smbrdr_hdr_setup(smbrdr_handle_t *srh)
{
static unsigned short my_pid = 0;
if (!my_pid)
my_pid = getpid();
return (smb_msgbuf_encode(&srh->srh_mbuf, "Mb4.bw12.wwww",
srh->srh_cmd,
srh->srh_session->smb_flags,
srh->srh_session->smb_flags2,
(srh->srh_tree) ? srh->srh_tree->tid : 0,
my_pid,
(srh->srh_user) ? srh->srh_user->uid : 0,
0 /* mid */));
}
/*
* Canned SMB header decode.
*/
static int
smb_decode_nt_hdr(smb_msgbuf_t *mb, smb_hdr_t *hdr)
{
return (smb_msgbuf_decode(mb, SMB_HEADER_NT_FMT,
&hdr->command,
&hdr->status.ntstatus,
&hdr->flags,
&hdr->flags2,
&hdr->pid_high,
SMB_SIG_SIZE,
&hdr->extra.extra.security_sig,
&hdr->tid,
&hdr->pid,
&hdr->uid,
&hdr->mid));
}
/*
* smbrdr_hdr_process
*
* Assuming 'srh->srh_mbuf' contains a response from a Windows client,
* decodes the 32 bytes SMB header.
*
* Buffer overflow typically means that the server has more data than
* it could fit in the response buffer. The client can use subsequent
* SmbReadX requests to obtain the remaining data (KB 193839).
*
* Returns:
*
* NT_STATUS_INVALID_NETWORK_RESPONSE error decoding the header
* NT_STATUS_REPLY_MESSAGE_MISMATCH response doesn't match the request
* NT_STATUS_SUCCESS successful
* smb_hdr->status.ntstatus error returned by server
*/
static DWORD
smbrdr_hdr_process(smbrdr_handle_t *srh, smb_hdr_t *smb_hdr)
{
int rc;
rc = smb_decode_nt_hdr(&srh->srh_mbuf, smb_hdr);
if (rc < SMB_HEADER_LEN) {
smb_log(smbrdr_log_hdl, LOG_DEBUG,
"smbrdr_hdr_process[%d]: invalid header (%d)",
srh->srh_cmd, rc);
return (NT_STATUS_INVALID_NETWORK_RESPONSE);
}
switch (NT_SC_VALUE(smb_hdr->status.ntstatus)) {
case NT_STATUS_SUCCESS:
case NT_STATUS_BUFFER_OVERFLOW:
break;
default:
smb_log(smbrdr_log_hdl, LOG_DEBUG,
"smbrdr_hdr_process[%d]: request failed (%s)",
srh->srh_cmd, xlate_nt_status(smb_hdr->status.ntstatus));
return (smb_hdr->status.ntstatus);
}
if (smb_hdr->command != srh->srh_cmd) {
smb_log(smbrdr_log_hdl, LOG_DEBUG,
"smbrdr_hdr_process[%d]: reply mismatch (%d)",
srh->srh_cmd, smb_hdr->command);
return (NT_STATUS_REPLY_MESSAGE_MISMATCH);
}
return (NT_STATUS_SUCCESS);
}
/*
* smbrdr_sign
*
* Signs the given outgoing packet according to the
* specified signing context.
*
* The client and server each maintain an integer counter
* which they initialize to zero. Both counters are
* incremented for every SMB message - that's once for a
* request and once for a reply. As a result, requests sent
* by SMB Redirector always have an even sequence number
* and replies from the Windows server always have an odd
* number.
*
* Based on the observed Windows 2003 behavior, any SMB
* request will fail with NT_STATUS_ACCESS_DENIED if its
* sequence number is not even.
*
* The function can fail if there is trouble with the cryptographic
* framework and if that happens SMBAUTH_FAILURE is returned. In the
* normal case SMBAUTH_SUCCESS is returned.
*/
static int
smbrdr_sign(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb)
{
if (sign_ctx->ssc_flags & SMB_SCF_STARTED) {
if (sign_ctx->ssc_seqnum % 2) {
smb_log(smbrdr_log_hdl, LOG_DEBUG,
"smbrdr_sign: invalid sequence (%d)",
sign_ctx->ssc_seqnum);
}
if (smb_mac_sign(sign_ctx, smb_msgbuf_base(mb),
smb_msgbuf_used(mb)) != SMBAUTH_SUCCESS)
return (SMBAUTH_FAILURE);
sign_ctx->ssc_seqnum++;
}
return (SMBAUTH_SUCCESS);
}
/*
* smbrdr_sign_chk
*
* Validates SMB MAC signature in the in-coming message.
* Return 1 if the signature are match; otherwise, return 0;
*
* When packet signing is enabled, the sequence number kept in the
* sign_ctx structure will be incremented when a SMB request is
* sent and upon the receipt of the first SmbTransact response
* if SMB fragmentation occurs.
*/
static int
smbrdr_sign_chk(smb_sign_ctx_t *sign_ctx, smb_msgbuf_t *mb,
unsigned char *signature)
{
int sign_ok = 1;
if (sign_ctx->ssc_flags & SMB_SCF_STARTED) {
(void) memcpy(sign_ctx->ssc_sign, signature, SMB_SIG_SIZE);
sign_ok = smb_mac_chk(sign_ctx, smb_msgbuf_base(mb),
smb_msgbuf_size(mb));
sign_ctx->ssc_seqnum++;
}
return (sign_ok);
}