#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <mach/task.h>
#include <mach/task_special_ports.h>
#include <mach/mig_errors.h>
#include <mach/vm_map.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <sys/smb_apple.h>
#include <netsmb/smb.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_gss.h>
#include <sys/kauth.h>
#include <fs/smbfs/smbfs.h>
#include <netsmb/smb_converter.h>
#include <gssd/gssd_mach_types.h>
#include <gssd/gssd_mach.h>
#include <sys/random.h>
static int der_length_size(int len)
{
return
len < (1 << 7) ? 1 :
len < (1 << 8) ? 2 :
len < (1 << 16) ? 3 :
len < (1 << 24) ? 4 : 5;
}
static void der_length_put(u_char **pp, int len)
{
int sz = der_length_size(len);
u_char *p = *pp;
if (sz == 1) {
*p++ = (u_char) len;
} else {
*p++ = (u_char) ((sz-1) | 0x80);
sz -= 1;
while (sz--)
*p++ = (u_char) ((len >> (sz * 8)) & 0xff);
}
*pp = p;
}
static int der_length_get(const uint8_t **dptr, const uint8_t *endptr, size_t *len)
{
size_t i;
const uint8_t *p = *dptr;
*len = 0;
if (*p & 0x80) {
size_t max_len = *p & 0x7f;
if (((max_len + p) > endptr) || (max_len > (sizeof (*len))))
return(EBADRPC);
for (i = max_len; i > 0; i--)
*len = (*len << 8) + *++p;
} else
*len = *p;
if (p >= endptr)
return(EBADRPC);
if (((*len+p+1) > endptr) || ((*len+p) < p)) {
SMBDEBUG("bad length field %ld\n", *len);
return(EBADRPC);
}
*dptr = p + 1;
return (0);
}
static uint8_t *parse_accept_complete_response(struct smb_gss *gp, const uint8_t *token,
const uint8_t *token_end, int *linenum)
{
size_t length;
if ((token >= token_end) || (*token++ != ASN1_CONTEXT_TAG(1))) {
*linenum = __LINE__;
return NULL;
}
if (der_length_get(&token, token_end, &length)) {
*linenum = __LINE__;
return NULL;
}
if ((token >= token_end) || (*token++ != ASN1_SEQUENCE_TAG(0))) {
*linenum = __LINE__;
return NULL;
}
if (der_length_get(&token, token_end, &length)) {
*linenum = __LINE__;
return NULL;
}
if ((token >= token_end) || (*token++ != ASN1_CONTEXT_TAG(0))) {
*linenum = __LINE__;
return NULL;
}
if (der_length_get(&token, token_end, &length)) {
*linenum = __LINE__;
return NULL;
}
if ((token >= token_end) || (*token++ != ASN1_ENUMERATED_TAG)) {
*linenum = __LINE__;
return NULL;
}
if (der_length_get(&token, token_end, &length)) {
*linenum = __LINE__;
return NULL;
}
if (token >= token_end) {
*linenum = __LINE__;
return NULL;
}
gp->gss_major = *token++;
return ((uint8_t *)token);
}
static int parse_ntlmssp_negotiate_auth_response(struct smb_vc *vcp)
{
struct smb_gss *gp = &vcp->vc_gss;
const uint8_t *token = gp->gss_token;
uint32_t token_len = gp->gss_tokenlen;
const uint8_t *token_end = token + token_len;
int linenum = 0;
token = parse_accept_complete_response(gp, token, token_end, &linenum);
if (token == NULL)
goto bad;
if (gp->gss_major != GSS_C_COMPLETE) {
SMBERROR("Expected complete, got gp->gss_major = %d\n", gp->gss_major);
linenum = __LINE__;
goto bad;
}
goto done;
bad:
gp->gss_major = GSS_C_COMPLETE;
SMBWARNING("Parsing error on line number - %d\n", linenum);
done:
if (gp->gss_token)
free(gp->gss_token, M_TEMP);
gp->gss_token = NULL;
gp->gss_tokenlen = 0;
return 0;
}
static void CheckIfServerForcesAllAuthenticationToGuest(struct smb_vc *vcp, uint16_t *startptr, uint16_t len)
{
uint16_t *endptr;
smb_hexdump(__FUNCTION__, "taget info", (void *)startptr, len);
endptr = startptr + len;
while ((startptr+2) < endptr) {
uint16_t AvId = htoles(*startptr++);
uint16_t AvLen = htoles(*startptr++);
switch (AvId) {
case kMsvAvFlags:
if (((startptr+sizeof(uint32_t)) > endptr) || (AvLen != sizeof(uint32_t))) {
SMBERROR("kMsvAvFlags: Bad size AvLen = %d\n", AvLen);
} else {
u_int32_t GuestAccessRequired = htolel(*(u_int32_t *)startptr);
SMBWARNING("kMsvAvFlags: GuestAccessRequired = %d\n", GuestAccessRequired);
if (GuestAccessRequired)
vcp->vc_flags |= SMBV_GUEST_ACCESS;
}
return;
break;
case kMsvAvNbComputerName:
case kMsvAvNbDomainName:
case kMsvAvDnsComputerName:
case kMsvAvDnsDomainName:
case kMsvAvDnsTreeName:
case kMsvAvTimestamp:
case kMsAvRestrictions:
startptr = (uint16_t *)((uint8_t *)startptr + AvLen);
SMBDEBUG("AvId = %d AvLen = %d startptr %p\n", AvId, AvLen, startptr);
break;
case kMsvAvEOL:
SMBDEBUG("kMsvAvEOL: Found the end\n");
return;
break;
default:
startptr = (uint16_t *)((uint8_t *)startptr + AvLen);
SMBWARNING("UNKNOWN AvId = %d AvLen = %d startptr %p\n", AvId, AvLen,startptr);
break;
}
}
}
static int parse_ntlmssp_negotiate_challenge(struct smb_vc *vcp, uint32_t *ntlmflags,
void **target_info, uint16_t *target_len)
{
struct smb_gss *gp = &vcp->vc_gss;
const uint8_t *token = gp->gss_token;
uint32_t token_len = gp->gss_tokenlen;
const uint8_t *token_end = token + token_len;
size_t length;
uint32_t *ntlmssp;
uint32_t ntlm_message_type;
struct SSPSecurityBuffer domain;
struct SSPSecurityBuffer address_list;
uint32_t local_context[2];
uint8_t ntlm_os_version[8];
int error = 0;
int linenum = 0;
const uint8_t *domain_offset_ptr = NULL;
const uint8_t *address_offset_ptr = NULL;
bzero(&domain, sizeof(domain));
bzero(&address_list, sizeof(address_list));
token = parse_accept_complete_response(gp, token, token_end, &linenum);
if (token == NULL)
goto bad;
if (gp->gss_major != GSS_C_CONTINUE_NEEDED) {
SMBERROR("Expected continue need, got gp->gss_major = %d\n", gp->gss_major);
linenum = __LINE__;
goto bad;
}
if ((token >= token_end) || (*token++ != ASN1_CONTEXT_TAG(1))) {
linenum = __LINE__;
goto bad;
}
if (der_length_get(&token, token_end, &length)) {
linenum = __LINE__;
goto bad;
}
if ((token >= token_end) || (*token++ != ASN1_OID_TAG)) {
linenum = __LINE__;
goto bad;
}
if (der_length_get(&token, token_end, &length)) {
linenum = __LINE__;
goto bad;
}
if (length != SPNEGO_mechType_NTLMSSP_LEN) {
linenum = __LINE__;
goto bad;
}
if (bcmp(token, SPNEGO_mechType_NTLMSSP, SPNEGO_mechType_NTLMSSP_LEN) != 0) {
linenum = __LINE__;
goto bad;
}
token += SPNEGO_mechType_NTLMSSP_LEN;
if ((token >= token_end) || (*token++ != ASN1_CONTEXT_TAG(2))) {
linenum = __LINE__;
goto bad;
}
if (der_length_get(&token, token_end, &length)) {
linenum = __LINE__;
goto bad;
}
if ((token >= token_end) || (*token++ != 0x04)) {
linenum = __LINE__;
goto bad;
}
if (der_length_get(&token, token_end, &length)) {
linenum = __LINE__;
goto bad;
}
if (length < sizeof(NTLMSSP_Signature)) {
linenum = __LINE__;
goto bad;
}
ntlmssp = (uint32_t *)token;
if (strncmp((char *)token, (char *)NTLMSSP_Signature, length) != 0) {
SMBERROR("The NTLMSSP_Signature messages is wrong!\n");
goto bad;
}
token += sizeof(NTLMSSP_Signature);
if ((token+sizeof(ntlm_message_type)) >= token_end) {
linenum = __LINE__;
goto bad;
}
ntlm_message_type = letohl(*(uint32_t *)token);
token += sizeof(ntlm_message_type);
if ( ntlm_message_type != NTLMSSP_TypeTwoMessage) {
SMBDEBUG("Wrong NTLM Message type got %d expected %d!\n", ntlm_message_type , NTLMSSP_TypeTwoMessage);
linenum = __LINE__;
goto bad;
}
if ((token+sizeof(domain)) >= token_end) {
linenum = __LINE__;
goto bad;
}
domain.length = letohs(*(uint16_t *)token);
token += sizeof(domain.length);
domain.allocated = letohs(*(uint16_t *)token);
token += sizeof(domain.allocated);
domain.offset = letohl(*(uint32_t *)token);
token += sizeof(domain.offset);
if (domain.offset > (VM_MAX_ADDRESS - (vm_offset_t)ntlmssp)) {
linenum = __LINE__;
goto bad;
}
domain_offset_ptr = ((uint8_t *)ntlmssp)+domain.offset;
if (domain_offset_ptr > token_end) {
linenum = __LINE__;
goto bad;
}
if ((domain.offset+domain.length) > (VM_MAX_ADDRESS - (vm_offset_t)ntlmssp)) {
linenum = __LINE__;
goto bad;
}
if ((domain_offset_ptr+domain.length) > token_end) {
linenum = __LINE__;
goto bad;
}
if ((domain.length == 0) || (domain.offset == 0))
domain_offset_ptr = token_end + 1;
if ((token+sizeof(ntlmflags)) > token_end) {
linenum = __LINE__;
goto bad;
}
*ntlmflags = letohl(*(uint32_t *)token);
token += sizeof(*ntlmflags);
SMBDEBUG("ntlmflags = 0x%x XP_CHALLENGE_NTLM_FLAGS = 0x%x\n", *ntlmflags, XP_CHALLENGE_NTLM_FLAGS);
#ifdef DEBUG_NTLM_BY_TURNING_OFF_NTLMV2
SMBERROR("Turning off NTLMV2 support\n");
vcp->vc_flags |= SMBV_NTLMV2_OFF;
#endif // DEBUG_NTLM_BY_TURNING_OFF_NTLMV2
if (vcp->vc_sopt.sv_caps & SMB_CAP_UNICODE) {
if ((*ntlmflags & NTLMSSP_NEGOTIATE_UNICODE) != NTLMSSP_NEGOTIATE_UNICODE) {
SMBERROR("Virtual Circuit is doing unicode, but ntlmssp doesn't support it!\n");
linenum = __LINE__;
goto bad;
}
} else {
if ((*ntlmflags & NTLM_NEGOTIATE_OEM) != NTLM_NEGOTIATE_OEM) {
SMBERROR("Virtual Circuit is not doing unicode, but ntlmssp doesn't support OEM!\n");
linenum = __LINE__;
goto bad;
}
}
if ((token+sizeof(vcp->vc_ch)) > token_end) {
linenum = __LINE__;
goto bad;
}
bcopy(token, vcp->vc_ch, sizeof(vcp->vc_ch));
token += sizeof(vcp->vc_ch);
smb_hexdump(__FUNCTION__, "vc_ch = ", vcp->vc_ch, sizeof(vcp->vc_ch));
if (((token+sizeof(local_context)) <= domain_offset_ptr) && (token_end >= (token+sizeof(local_context)))) {
local_context[0] = letohl(*(uint32_t *)token);
token += sizeof(local_context[0]);
local_context[1] = letohl(*(uint32_t *)token);
token += sizeof(local_context[1]);
} else {
SMBWARNING("local_context check failed: token = %p domain_offset_ptr = %p token_end = %p\n",
token, domain_offset_ptr, token_end);
goto skip_optional;
}
if (((token+sizeof(address_list)) <= domain_offset_ptr) && (token_end >= (token+sizeof(address_list)))) {
address_list.length = letohs(*(uint16_t *)token);
token += sizeof(address_list.length);
address_list.allocated = letohs(*(uint16_t *)token);
token += sizeof(address_list.allocated);
address_list.offset = letohl(*(uint32_t *)token);
token += sizeof(address_list.offset);
} else {
SMBWARNING("address_list check failed: token = %p domain_offset_ptr = %p token_end = %p\n",
token, domain_offset_ptr, token_end);
goto skip_optional;
}
if (((token+sizeof(ntlm_os_version)) <= domain_offset_ptr) && (token_end >= (token+sizeof(ntlm_os_version)))) {
bcopy(token, ntlm_os_version, sizeof(ntlm_os_version));
token += sizeof(ntlm_os_version);
} else {
SMBWARNING("ntlm_os_version check failed: token = %p domain_offset_ptr = %p token_end = %p\n",
token, domain_offset_ptr, token_end);
goto skip_optional;
}
skip_optional:
if (domain_offset_ptr >= token_end) {
SMBERROR("Bad Domain offset/length: length = %d allocated = %d offset = %d\n", domain.length, domain.allocated, domain.offset);
goto bad;
}
if (((vcp->vc_domain == NULL) || (vcp->vc_domain[0] == 0)) && (vcp->vc_flags & SMBV_SERVER_DOMAIN)) {
char * domain_str = NULL;
if (*ntlmflags & NTLMSSP_NEGOTIATE_UNICODE) {
size_t maxlen = (domain.length * 3) + 2;
domain_str = malloc(maxlen, M_SMBTEMP, M_WAITOK);
if (domain_str)
smb_unitostr(domain_str, (const u_int16_t *)domain_offset_ptr, domain.length, maxlen, 0);
} else {
domain_str = malloc(domain.length+1, M_SMBTEMP, M_WAITOK);
if (domain_str) {
memcpy(domain_str, (char *)domain_offset_ptr, domain.length);
domain_str[domain.length] = 0;
}
}
if (domain_str) {
SMB_STRFREE(vcp->vc_domain);
vcp->vc_domain = domain_str;
SMBWARNING("Using servers domain %s, use the use_server_domain option to turn this off.\n", domain_str);
}
}
SMBDEBUG(" Target Info length = %d allocated = %d offset = %d\n", address_list.length, address_list.allocated, address_list.offset);
if ((address_list.length == 0) || (address_list.allocated == 0)) {
SMBDEBUG("No target information length = %d allocated = %d offset = %d!\n", address_list.length, address_list.allocated, address_list.offset);
error = 0;
goto done;
}
if (address_list.offset > (VM_MAX_ADDRESS - (vm_offset_t)ntlmssp)) {
linenum = __LINE__;
goto bad;
}
address_offset_ptr = ((uint8_t *)ntlmssp)+address_list.offset;
if (address_offset_ptr > token_end) {
linenum = __LINE__;
goto bad;
}
if ((address_list.offset+address_list.length) > (VM_MAX_ADDRESS - (vm_offset_t)ntlmssp)) {
linenum = __LINE__;
goto bad;
}
if ((address_offset_ptr+address_list.length) > token_end) {
linenum = __LINE__;
goto bad;
}
*target_info = malloc(address_list.length, M_SMBTEMP, M_WAITOK);
if (*target_info) {
memcpy(*target_info, (void *)address_offset_ptr, address_list.length);
*target_len = address_list.length;
CheckIfServerForcesAllAuthenticationToGuest(vcp, (uint16_t *)*target_info, *target_len);
}
error = 0;
goto done;
bad:
if (!error)
error = EBADRPC;
gp->gss_major = GSS_C_COMPLETE;
if (linenum)
SMBWARNING("Parsing error on line number - %d error = %d\n", linenum, error);
done:
if (gp->gss_token)
free(gp->gss_token, M_TEMP);
gp->gss_token = NULL;
gp->gss_tokenlen = 0;
return error;
}
static int create_ntlmssp_negotiate_auth(struct smb_vc *vcp, uint32_t ntlmflags,
void *target_info, u_int16_t target_len)
{
struct smb_gss *gp = &vcp->vc_gss;
uint32_t *ntlmssp;
int error = 0;
uint32_t LengthFieldBytes;
uint32_t ntlmssp_len;
uint32_t LengthField[4];
struct SSPSecurityBuffer secbuffer;
u_char *pptr, *start_ntlm, *offset_ptr;
void *lmv2 = NULL, *ntlmv2 = NULL;
size_t lmv2_len = 0, ntlmv2_len = 0;
u_int16_t *username = NULL;
u_int16_t *hostname = NULL;
u_int16_t *domain_str = NULL;
u_int64_t server_nonce = *(u_int64_t *)vcp->vc_ch;
size_t hostname_len = strnlen(vcp->vc_localname, SMB_MAXNetBIOSNAMELEN+1);
size_t username_len = strnlen(vcp->vc_username, SMB_MAXUSERNAMELEN + 1);
size_t domain_len = strnlen(vcp->vc_domain, SMB_MAXNetBIOSNAMELEN + 1);
size_t keyExchange_len = 0;
u_int8_t *keyExchange = NULL;
u_int16_t length;
DBG_ASSERT(gp->gss_token == NULL);
#ifdef SMB_DEBUG
if (vcp->vc_flags & SMBV_ANONYMOUS_ACCESS) {
DBG_ASSERT(username_len == 0)
DBG_ASSERT(domain_len == 0)
}
#endif // SMB_DEBUG
if (domain_len) {
domain_len += 1;
domain_str = malloc(domain_len * 2, M_SMBNODENAME, M_WAITOK);
if (!domain_str) {
SMBDEBUG("Can't allocate domain_str pointer?\n");
error = ENOMEM;
goto done;
}
domain_len = smb_strtouni(domain_str, (const char *)vcp->vc_domain, domain_len, UTF_NO_NULL_TERM);
}
if (username_len) {
username_len += 1;
username = malloc(username_len * 2, M_SMBNODENAME, M_WAITOK);
if (!username) {
SMBDEBUG("Can't allocate username pointer\n");
error = ENOMEM;
goto done;
}
username_len = smb_strtouni(username, (const char *)vcp->vc_username, username_len, UTF_NO_NULL_TERM);
}
if (hostname_len) {
hostname_len += 1;
hostname = malloc(hostname_len * 2, M_SMBNODENAME, M_WAITOK);
if (!hostname) {
SMBDEBUG("Can't allocate hostname pointer\n");
error = ENOMEM;
goto done;
}
hostname_len = smb_strtouni(hostname, (const char *)vcp->vc_localname, hostname_len, UTF_NO_NULL_TERM);
}
if (vcp->vc_flags & SMBV_ANONYMOUS_ACCESS) {
ntlmv2_len = 0;
ntlmv2 = NULL;
lmv2 = NULL;
lmv2_len = 1;
} else if ((vcp->vc_flags & SMBV_NTLMV2_OFF) != SMBV_NTLMV2_OFF) {
uint8_t ntlmv2Hash[SMB_NTLMV2_LEN];
u_int64_t client_nonce;
read_random((void *)&client_nonce, (u_int)sizeof(client_nonce));
smb_ntlmv2hash(ntlmv2Hash, vcp->vc_domain, vcp->vc_uppercase_username, smb_vc_getpass(vcp));
lmv2 = smb_lmv2_response(ntlmv2Hash, server_nonce, client_nonce, &lmv2_len);
if (lmv2 == NULL)
lmv2_len = 0;
ntlmv2 = make_ntlmv2_blob(client_nonce, target_info, target_len, &ntlmv2_len);
if (ntlmv2) {
smb_ntlmv2_response(ntlmv2Hash, ntlmv2, ntlmv2_len, server_nonce);
smb_calcv2mackey(vcp, ntlmv2Hash, ntlmv2, NULL, 0);
}
} else {
if (vcp->vc_flags & SMBV_MINAUTH_NTLM) {
lmv2 = NULL;
lmv2_len = 0;
} else {
lmv2_len = 24;
lmv2 = malloc(lmv2_len, M_SMBTEMP, M_WAITOK);
if (lmv2)
smb_lmresponse((u_char *)smb_vc_getpass(vcp), (u_char *)&server_nonce, (u_char *)lmv2);
else
lmv2_len = 0;
}
ntlmv2_len = 24;
ntlmv2 = malloc(ntlmv2_len, M_SMBTEMP, M_WAITOK);
if (ntlmv2)
smb_ntlmresponse((u_char *)smb_vc_getpass(vcp), (u_char *)&server_nonce, (u_char*)ntlmv2);
else
ntlmv2_len = 0;
smb_calcmackey(vcp, NULL, 0);
}
ntlmssp_len = (uint32_t)(sizeof(NTLMSSP_Signature) + sizeof(uint32_t) +
(sizeof(struct SSPSecurityBuffer) * 6) + sizeof(ntlmflags) +
lmv2_len + ntlmv2_len + username_len + hostname_len + domain_len +
keyExchange_len);
#ifdef SMB_DEBUG
ntlmssp_len += NTLMSSP_NEGOTIATE_VERSION_LEN;
#endif // SMB_DEBUG
gp->gss_tokenlen = 2 + 2;
LengthFieldBytes = der_length_size(ntlmssp_len);
LengthField[0] = ntlmssp_len + LengthFieldBytes + 1;
LengthFieldBytes += der_length_size(LengthField[0]);
LengthField[1] = ntlmssp_len + LengthFieldBytes + 2;
LengthFieldBytes += der_length_size(LengthField[1]);
LengthField[2] = ntlmssp_len + LengthFieldBytes + 3;
LengthFieldBytes += der_length_size(LengthField[2]);
gp->gss_tokenlen += LengthFieldBytes + ntlmssp_len;
gp->gss_token = malloc(gp->gss_tokenlen, M_SMBTEMP, M_WAITOK);
if (gp->gss_token == NULL) {
SMBERROR("Can't allocate token pointer\n");
error = ENOMEM;
goto done;
}
memset(gp->gss_token, 0, gp->gss_tokenlen);
pptr = gp->gss_token;
*pptr++ = ASN1_CONTEXT_TAG(1);
der_length_put(&pptr, LengthField[2]);
*pptr++ = ASN1_SEQUENCE_TAG(0);
der_length_put(&pptr, LengthField[1]);
*pptr++ = ASN1_CONTEXT_TAG(2);
der_length_put(&pptr, LengthField[0]);
*pptr++ = 0x04;
der_length_put(&pptr, ntlmssp_len);
start_ntlm = pptr;
strlcpy((char *)pptr, NTLMSSP_Signature, ntlmssp_len);
pptr += sizeof(NTLMSSP_Signature);
ntlmssp = (uint32_t *)pptr;
*ntlmssp++ = letohl(NTLMSSP_TypeThreeMessage);
pptr = (void *)ntlmssp;
offset_ptr = start_ntlm + ntlmssp_len - (lmv2_len + ntlmv2_len);
length = (u_int16_t)lmv2_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (lmv2)
memcpy(offset_ptr, lmv2, lmv2_len);
offset_ptr += lmv2_len;
length = (u_int16_t)ntlmv2_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (ntlmv2)
memcpy(offset_ptr, ntlmv2, ntlmv2_len);
offset_ptr = start_ntlm + ntlmssp_len - (lmv2_len + ntlmv2_len + username_len
+ hostname_len + domain_len + keyExchange_len);
length = (u_int16_t)domain_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (domain_str)
memcpy(offset_ptr, domain_str, domain_len);
offset_ptr += domain_len;
length = (u_int16_t)username_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (username)
memcpy(offset_ptr, username, username_len);
offset_ptr += username_len;
length = (u_int16_t)hostname_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (hostname)
memcpy(offset_ptr, hostname, hostname_len);
offset_ptr += hostname_len;
if (ntlmflags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
SMBERROR("Key exchange not currently supported\n");
} else {
DBG_ASSERT(keyExchange_len == 0);
DBG_ASSERT(keyExchange == NULL);
}
length = (u_int16_t)keyExchange_len;
secbuffer.length = letohs(length);
secbuffer.allocated = secbuffer.length;
secbuffer.offset = letohl(offset_ptr - start_ntlm);
memcpy(pptr, (void *)&secbuffer, sizeof(secbuffer));
pptr += sizeof(secbuffer);
if (keyExchange)
memcpy(offset_ptr, keyExchange, keyExchange_len);
ntlmssp = (uint32_t *)pptr;
ntlmflags = SMB_AUTH_NTLM_FLAGS;
#ifdef SMB_DEBUG
ntlmflags |= NTLMSSP_NEGOTIATE_VERSION;
#endif // SMB_DEBUG
if (vcp->vc_flags & SMBV_ANONYMOUS_ACCESS)
ntlmflags |= NTLMSSP_RESERVED_7;
if (vcp->vc_sopt.sv_caps & SMB_CAP_UNICODE)
ntlmflags |= NTLMSSP_NEGOTIATE_UNICODE;
else
ntlmflags |= NTLM_NEGOTIATE_OEM;
if (vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE)
ntlmflags |= NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
*ntlmssp++ = letohl(ntlmflags);
#ifdef SMB_DEBUG
{
uint8_t *version = (uint8_t *)ntlmssp;
*version++ = WINDOWS_MAJOR_VERSION_6;
*version++ = WINDOWS_MINOR_VERSION_0;
*((uint16_t *)version) = WINDOWS_BUILD_VERSION;
version += 5;
*version++ = NTLMSSP_REVISION_W2K3;
}
#endif // SMB_DEBUG
done:
if (target_info)
free(target_info, M_SMBTEMP);
if (lmv2)
free(lmv2, M_TEMP);
if (ntlmv2)
free(ntlmv2, M_TEMP);
if (username)
free(username, M_TEMP);
if (hostname)
free(hostname, M_TEMP);
if (domain_str)
free(domain_str, M_TEMP);
if (error) {
if (gp->gss_token)
free(gp->gss_token, M_TEMP);
gp->gss_token = NULL;
gp->gss_tokenlen = 0;
}
return error;
}
static int create_ntlmssp_negotiate_message(struct smb_vc *vcp)
{
uint8_t *token, *pptr;
uint32_t *ntlmssp;
uint32_t ntlmssp_len;
uint32_t LengthField[4];
uint32_t token_len;
uint32_t LengthFieldBytes;
uint32_t ntlmflags = 0;
struct smb_gss *gp = &vcp->vc_gss;
ntlmssp_len = (uint32_t)(sizeof(NTLMSSP_Signature) + sizeof(uint32_t) +
sizeof(ntlmflags) + sizeof(struct SSPSecurityBuffer) +
sizeof(struct SSPSecurityBuffer));
#ifdef SMB_DEBUG
ntlmssp_len += NTLMSSP_NEGOTIATE_VERSION_LEN;
#endif // SMB_DEBUG
token_len = ASN1_APPLICATION_TAG_LEN +
1 + 1 + SPNEGO_OID_1_3_6_1_5_5_2_LEN +
1 + 1 +
1 + 1 + 1 + 1 +
1 + 1 + SPNEGO_mechType_NTLMSSP_LEN +
+ 1 + 1;
LengthFieldBytes = der_length_size(ntlmssp_len);
LengthField[0] = ntlmssp_len + LengthFieldBytes + 1;
LengthFieldBytes += der_length_size(LengthField[0]);
LengthField[1] = ntlmssp_len + LengthFieldBytes + 2 + 2 + SPNEGO_mechType_NTLMSSP_LEN + 4;
LengthFieldBytes += der_length_size(LengthField[1]);
LengthField[2] = ntlmssp_len + LengthFieldBytes + 2 + 2 + SPNEGO_mechType_NTLMSSP_LEN + 4 + 1;
LengthFieldBytes += der_length_size(LengthField[2]);
LengthField[3] = ntlmssp_len + LengthFieldBytes + token_len - 1;
LengthFieldBytes += der_length_size(ntlmssp_len + LengthFieldBytes + token_len - 1);
token_len += LengthFieldBytes + ntlmssp_len;
token = malloc(token_len, M_SMBTEMP, M_WAITOK);
if (token == NULL) {
SMBERROR("Can't allocate token pointer\n");
return ENOMEM;
}
memset(token, 0, token_len);
pptr = token;
*pptr++ = ASN1_APPLICATION_TAG(0);
der_length_put(&pptr, LengthField[3]);
*pptr++ = ASN1_OID_TAG;
*pptr++ = SPNEGO_OID_1_3_6_1_5_5_2_LEN;
bcopy(SPNEGO_OID_1_3_6_1_5_5_2, pptr, SPNEGO_OID_1_3_6_1_5_5_2_LEN);
pptr += SPNEGO_OID_1_3_6_1_5_5_2_LEN;
*pptr++ = ASN1_CONTEXT_TAG(0);
der_length_put(&pptr, LengthField[2]);
*pptr++ = ASN1_SEQUENCE_TAG(0);
der_length_put(&pptr, LengthField[1]);
*pptr++ = ASN1_CONTEXT_TAG(0);
der_length_put(&pptr, SPNEGO_mechType_NTLMSSP_LEN + 2 + 2);
*pptr++ = ASN1_SEQUENCE_TAG(0);
der_length_put(&pptr, SPNEGO_mechType_NTLMSSP_LEN + 2);
*pptr++ = ASN1_OID_TAG;
*pptr++ = SPNEGO_mechType_NTLMSSP_LEN;
bcopy(SPNEGO_mechType_NTLMSSP, pptr, SPNEGO_mechType_NTLMSSP_LEN);
pptr += SPNEGO_mechType_NTLMSSP_LEN;
*pptr++ = ASN1_CONTEXT_TAG(2);
der_length_put(&pptr, LengthField[0]);
*pptr++ = 0x04;
der_length_put(&pptr, ntlmssp_len);
strlcpy((char *)pptr, NTLMSSP_Signature, ntlmssp_len);
pptr += sizeof(NTLMSSP_Signature);
ntlmssp = (uint32_t *)pptr;
*ntlmssp++ = letohl(NTLMSSP_TypeOneMessage);
ntlmflags = SMB_NEGOTIATE_NTLM_FLAGS;
#ifdef SMB_DEBUG
ntlmflags |= NTLMSSP_NEGOTIATE_VERSION;
#endif // SMB_DEBUG
if (vcp->vc_sopt.sv_caps & SMB_CAP_UNICODE)
ntlmflags |= NTLMSSP_NEGOTIATE_UNICODE;
else
ntlmflags |= NTLM_NEGOTIATE_OEM;
if (vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE)
ntlmflags |= NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
*ntlmssp++ = letohl(ntlmflags);
#ifdef SMB_DEBUG
{
uint8_t *version = (uint8_t *)ntlmssp;
version += (int)(sizeof(struct SSPSecurityBuffer) + sizeof(struct SSPSecurityBuffer));
*version++ = WINDOWS_MAJOR_VERSION_6;
*version++ = WINDOWS_MINOR_VERSION_0;
*((uint16_t *)version) = WINDOWS_BUILD_VERSION;
version += 5;
*version++ = NTLMSSP_REVISION_W2K3;
}
#endif // SMB_DEBUG
gp->gss_token = token;
gp->gss_tokenlen = token_len;
gp->gss_major = GSS_C_CONTINUE_NEEDED;
return 0;
}
static int
smb_asn1_blob2principal(struct smb_vc *vcp, const uint8_t *blobp, u_int16_t blob_len,
char **princ, size_t *princlen)
{
size_t ellen;
size_t mechs_ellen;
size_t mechlen;
uint8_t *endOfMechTypes = NULL;
uint8_t *blobp_end = (uint8_t *)blobp + blob_len;
*princ = NULL;
*princlen = 0;
if ((blobp >= blobp_end) || (*blobp++ != ASN1_APPLICATION_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp+SPNEGO_INIT_TOKEN_LEN) > blobp_end)
return (0);
if (bcmp(blobp, SPNEGO_INIT_TOKEN, SPNEGO_INIT_TOKEN_LEN) != 0)
return (0);
blobp += SPNEGO_INIT_TOKEN_LEN;
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp >= blobp_end) || (*blobp++ != ASN1_SEQUENCE_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp >= blobp_end) || (*blobp++ != ASN1_CONTEXT_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp >= blobp_end) || (*blobp++ != ASN1_SEQUENCE_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &mechs_ellen))
return (0);
#ifdef SMB_DEBUG
if (mechs_ellen != (ellen - 2))
SMBERROR("mechs_ellen = %x ellen = %x\n", (unsigned int)mechs_ellen, (unsigned int)ellen);
#endif // SMB_DEBUG
endOfMechTypes = (uint8_t *)blobp + mechs_ellen;
if (endOfMechTypes > blobp_end)
return (0);
while (endOfMechTypes > blobp) {
if (*blobp++ != ASN1_OID_TAG) {
SMBERROR("Bad Identifier octet (6=OID)\n");
blobp = endOfMechTypes;
break;
}
if ((der_length_get(&blobp, endOfMechTypes, &mechlen)) || (mechlen == 0)) {
SMBERROR("Bad mechlen length!\n");
blobp = endOfMechTypes;
break;
}
if ((mechlen+blobp) > endOfMechTypes) {
SMBERROR("Bad mechlen length, too long! mechlen = %ld\n", mechlen);
blobp = endOfMechTypes;
break;
}
#ifdef DEBUG_MECH_TYPES
smb_hexdump(__FUNCTION__, "mechType: ", (u_char *)blobp, mechlen);
#endif // DEBUG_MECH_TYPES
if ((mechlen == SPNEGO_mechType_MSKRB5_LEN) &&
(bcmp(blobp, SPNEGO_mechType_MSKRB5, SPNEGO_mechType_MSKRB5_LEN) == 0))
vcp->vc_flags |= SMBV_MECHTYPE_KRB5;
else if ((mechlen == SPNEGO_mechType_KRB5_LEN) &&
(bcmp(blobp, SPNEGO_mechType_KRB5, SPNEGO_mechType_KRB5_LEN) == 0))
vcp->vc_flags |= SMBV_MECHTYPE_KRB5;
else if ((mechlen == SPNEGO_mechType_KRB5_V3_LEN) &&
(bcmp(blobp, SPNEGO_mechType_KRB5, SPNEGO_mechType_KRB5_V3_LEN) == 0))
vcp->vc_flags |= SMBV_MECHTYPE_KRB5;
else if ((mechlen == SPNEGO_mechType_NTLMSSP_LEN) &&
(bcmp(blobp, SPNEGO_mechType_NTLMSSP, SPNEGO_mechType_NTLMSSP_LEN) == 0))
vcp->vc_flags |= SMBV_MECHTYPE_NTLMSSP;
blobp += mechlen;
}
if ((vcp->vc_flags & SMBV_MECHTYPE_KRB5) != SMBV_MECHTYPE_KRB5)
return 0;
if (blobp != endOfMechTypes) {
SMBERROR("Something bad happen blobp = %p endOfMechTypes = %p!\n", blobp, endOfMechTypes);
blobp = endOfMechTypes;
}
if ((blobp < blobp_end) && (*blobp == ASN1_CONTEXT_TAG(2))) {
blobp += 1;
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
blobp += ellen;
}
if ((blobp >= blobp_end) || (*blobp++ != ASN1_CONTEXT_TAG(3)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp <= blobp_end) && (ASN1_STRING_TYPE(*blobp)))
goto have_string;
if ((blobp >= blobp_end) || (*blobp++ != ASN1_SEQUENCE_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp > blobp_end) || (*blobp++ != ASN1_CONTEXT_TAG(0)))
return (0);
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp > blobp_end) || (!ASN1_STRING_TYPE(*blobp)))
return (0);
have_string:
blobp += 1;
if (der_length_get(&blobp, blobp_end, &ellen))
return (0);
if ((blobp + ellen) > blobp_end)
return (0);
*princ = (char *)blobp;
*princlen = ellen;
SMBDEBUG("princ = %s\n", *princ);
return (1);
}
static int
smb_reply2principal(struct smb_vc *vcp, const uint8_t *rp, u_int16_t rplen, char **principal, uint32_t *princlen)
{
char *spn;
size_t spnlen;
char *p, *endp;
char *service, *s_end;
char *host, *h_end;
char *realm;
size_t left;
*principal = NULL;
*princlen = 0;
if (!smb_asn1_blob2principal(vcp, rp, rplen, &spn, &spnlen))
return (0);
*princlen = (uint32_t)spnlen;
endp = spn + spnlen;
service = spn;
for (host = spn; (host < endp) && (*host != '/'); host++)
;
if (host == endp) {
host = spn;
service = (char *)"cifs";
s_end = service + 4;
*princlen +=5;
} else {
s_end = host++;
}
for (realm = host; (realm < endp) && (*realm != '@'); realm++)
;
if (realm < endp && realm > host) {
h_end = realm;
if (*(h_end - 1) == '$') {
h_end -= 1;
*princlen -= 1;
}
realm += 1;
left = endp - realm;
} else {
h_end = endp;
left = 0;
}
*princlen += 1;
MALLOC(*principal, char *, *princlen, M_TEMP, M_WAITOK);
p = *principal;
bcopy(service, p, s_end - service);
p += (s_end - service);
*p++ = '/';
bcopy(host, p, h_end - host);
p += (h_end - host);
if (left) {
*p++ = '@';
bcopy(realm, p, left);
p += left;
}
*p = '\0';
DBG_ASSERT(strnlen(*principal, SMB_MAX_KERB_PN+1)+1 == *princlen);
SMBDEBUG("principal name is %s, len is %d\n", *principal, *princlen);
return (1);
}
int
smb_gss_negotiate(struct smb_vc *vcp, vfs_context_t context, uint8_t *token, u_int16_t toklen)
{
struct smb_gss *gp = &vcp->vc_gss;
kern_return_t kr;
if (IPC_PORT_VALID(gp->gss_mp))
return 0;
DBG_ASSERT(context);
if (context == NULL)
return EPIPE;
if (gp->gss_spn)
free(gp->gss_spn, M_TEMP);
gp->gss_spn = NULL;
if (toklen > SMB_GUIDLEN) {
token += SMB_GUIDLEN;
toklen -= SMB_GUIDLEN;
if (!smb_reply2principal(vcp, token, toklen, &gp->gss_spn, &gp->gss_spnlen))
vcp->vc_flags |= SMBV_MECHTYPE_NTLMSSP;
} else
vcp->vc_flags |= SMBV_MECHTYPE_NTLMSSP;
SMBDEBUG("vcp->vc_flags = 0x%x gp->gss_spn = %s\n", vcp->vc_flags, (gp->gss_spn) ? gp->gss_spn : "NULL");
kr = vfs_context_get_special_port(context, TASK_GSSD_PORT, &gp->gss_mp);
if (kr != KERN_SUCCESS || !IPC_PORT_VALID(gp->gss_mp)) {
SMBERROR("Can't get gssd port, status %d\n", kr);
if (gp->gss_spn)
free(gp->gss_spn, M_TEMP);
gp->gss_spn = NULL;
return EPIPE;
}
return 0;
}
static void
smb_gss_reset(struct smb_gss *gp)
{
if (gp->gss_token)
free(gp->gss_token, M_TEMP);
gp->gss_token = NULL;
gp->gss_tokenlen = 0;
gp->gss_ctx = 0;
gp->gss_cred = 0;
gp->gss_major = 0;
gp->gss_minor = 0;
}
void
smb_gss_destroy(struct smb_gss *gp)
{
extern void ipc_port_release_send(ipc_port_t);
if (IPC_PORT_VALID(gp->gss_mp))
ipc_port_release_send(gp->gss_mp);
if (gp->gss_cpn)
free(gp->gss_cpn, M_TEMP);
if (gp->gss_spn)
free(gp->gss_spn, M_TEMP);
if (gp->gss_token)
free(gp->gss_token, M_TEMP);
bzero(gp, sizeof(struct smb_gss));
}
static void
gss_mach_alloc_buffer(u_char *buf, uint32_t buflen, vm_map_copy_t *addr)
{
kern_return_t kr;
vm_offset_t kmem_buf;
vm_size_t tbuflen;
*addr = NULL;
if (buf == NULL || buflen == 0)
return;
tbuflen = round_page(buflen);
kr = vm_allocate(ipc_kernel_map, &kmem_buf, tbuflen, VM_FLAGS_ANYWHERE);
if (kr != 0) {
SMBERROR("gss_mach_alloc_buffer: vm_allocate failed\n");
return;
}
kr = vm_map_wire(ipc_kernel_map, vm_map_trunc_page(kmem_buf),
vm_map_round_page(kmem_buf + tbuflen),
VM_PROT_READ|VM_PROT_WRITE, FALSE);
bcopy(buf, (void *) kmem_buf, buflen);
kr = vm_map_unwire(ipc_kernel_map, vm_map_trunc_page(kmem_buf),
vm_map_round_page(kmem_buf + tbuflen), FALSE);
if (kr != 0) {
SMBERROR("gss_mach_alloc_buffer: vm_map_unwire failed\n");
return;
}
kr = vm_map_copyin(ipc_kernel_map, (vm_map_address_t) kmem_buf,
(vm_map_size_t) buflen, TRUE, addr);
if (kr != 0) {
SMBERROR("gss_mach_alloc_buffer: vm_map_copyin failed\n");
return;
}
}
static int
gss_mach_vmcopyout(vm_map_copy_t in, uint32_t len, u_char *out)
{
vm_map_offset_t map_data;
vm_offset_t data;
int error;
error = vm_map_copyout(ipc_kernel_map, &map_data, in);
if (error)
return (error);
data = CAST_DOWN(vm_offset_t, map_data);
bcopy((void *) data, out, len);
vm_deallocate(ipc_kernel_map, data, len);
return (0);
}
static void
gss_mach_vm_map_copy_discard(vm_map_copy_t copy, mach_msg_type_number_t len)
{
vm_map_offset_t map_data;
if (vm_map_copyout(ipc_kernel_map, &map_data, copy))
return;
vm_deallocate(ipc_kernel_map, CAST_DOWN(vm_offset_t, map_data), (vm_size_t)len);
}
static kern_return_t smb_gss_init_ntlmssp(struct smb_vc *vcp)
{
int error;
struct smb_gss *gp = &vcp->vc_gss;
if (gp->gss_ctx == NTLMSSP_NEGOTIATE) {
error = create_ntlmssp_negotiate_message(vcp);
gp->gss_ctx = NTLMSSP_CHALLENGE;
} else if (gp->gss_ctx == NTLMSSP_CHALLENGE) {
uint32_t ntlmflags = 0;
void *target_info = NULL;
uint16_t target_len = 0;
if (gp->gss_token)
error = parse_ntlmssp_negotiate_challenge(vcp, &ntlmflags, &target_info, &target_len);
else {
error = EBADRPC;
SMBERROR("No token returned from negotiate challenge message\n");
}
if (error == 0)
error = create_ntlmssp_negotiate_auth(vcp, ntlmflags, target_info, target_len);
gp->gss_ctx = NTLMSSP_AUTH;
} else {
DBG_ASSERT(gp->gss_ctx == NTLMSSP_AUTH);
if (gp->gss_tokenlen && gp->gss_token) {
smb_hexdump(__FUNCTION__, "Parsing negotiate_auth_response ", gp->gss_token, gp->gss_tokenlen);
error = parse_ntlmssp_negotiate_auth_response(vcp);
} else {
gp->gss_major = GSS_C_COMPLETE;
gp->gss_tokenlen = 0;
if (gp->gss_smb_error == EAGAIN) {
SMBERROR("NTLMSSP Complete, but we got an EAGAIN error?\n");
error = EAUTH;
} else
error = 0;
}
gp->gss_ctx = NTLMSSP_DONE;
}
return error;
}
static kern_return_t
smb_gss_init(struct smb_vc *vcp, uid_t uid)
{
struct smb_gss *cp = &vcp->vc_gss;
int win2k = (vcp->vc_flags & SMBV_WIN2K_XP) ? GSSD_WIN2K_HACK : 0;
kern_return_t kr;
byte_buffer okey = NULL;
int retry_cnt = 0;
vm_map_copy_t itoken = NULL;
byte_buffer otoken = NULL;
mach_msg_type_number_t otokenlen;
int error = 0;
const char *gss_cpn = (cp->gss_cpn) ? cp->gss_cpn : "";
if (!IPC_PORT_VALID(cp->gss_mp)) {
SMBWARNING("smb_gss_init: gssd port not valid\n");
goto out;
}
if (cp->gss_tokenlen > 0)
gss_mach_alloc_buffer(cp->gss_token, cp->gss_tokenlen, &itoken);
retry:
kr = mach_gss_init_sec_context(
cp->gss_mp,
SPNEGO_MECH,
(byte_buffer) itoken, (mach_msg_type_number_t) cp->gss_tokenlen,
uid,
(char *)gss_cpn,
cp->gss_spn,
GSSD_MUTUAL_FLAG | GSSD_C_DELEG_POLICY_FLAG,
win2k,
&cp->gss_ctx,
&cp->gss_cred,
&cp->gss_rflags,
&okey, (mach_msg_type_number_t *) &vcp->vc_mackeylen,
&otoken, (mach_msg_type_number_t *) &otokenlen,
&cp->gss_major,
&cp->gss_minor);
if (kr != 0) {
SMBERROR("smb_gss_init: mach_gss_init_sec_context failed: %x %d\n", kr, kr);
if (kr == MIG_SERVER_DIED && cp->gss_cred == 0 &&
retry_cnt++ < GSS_MACH_MAX_RETRIES) {
if (cp->gss_tokenlen > 0)
gss_mach_alloc_buffer(cp->gss_token, cp->gss_tokenlen, &itoken);
goto retry;
}
goto out;
}
if (vcp->vc_mackeylen > 0) {
#ifdef SMB_DEBUG
if (vcp->vc_mackeylen != SKEYLEN)
SMBERROR("smb_gss_init: non-nfs key length (%d)\n", vcp->vc_mackeylen);
else
SMBERROR("smb_gss_init: key length (%d)\n", vcp->vc_mackeylen);
#endif // SMB_DEBUG
if (vcp->vc_mackey)
FREE(vcp->vc_mackey, M_TEMP);
MALLOC(vcp->vc_mackey, uint8_t *, vcp->vc_mackeylen, M_TEMP, M_WAITOK);
error = gss_mach_vmcopyout((vm_map_copy_t) okey, vcp->vc_mackeylen, vcp->vc_mackey);
if (error) {
gss_mach_vm_map_copy_discard((vm_map_copy_t)otoken, otokenlen);
goto out;
}
}
if (cp->gss_token)
FREE(cp->gss_token, M_TEMP);
cp->gss_token = NULL;
cp->gss_tokenlen = 0;
if (otokenlen > 0) {
MALLOC(cp->gss_token, uint8_t *, otokenlen, M_TEMP, M_WAITOK);
error = gss_mach_vmcopyout((vm_map_copy_t) otoken, otokenlen, cp->gss_token);
if (error)
goto out;
cp->gss_tokenlen = otokenlen;
}
if (SMB_GSS_ERROR(cp) && (vcp->vc_iod->iod_flags & SMBIOD_RECONNECT))
return EAGAIN;
return (0);
out:
if (cp->gss_token)
FREE(cp->gss_token, M_TEMP);
cp->gss_token = NULL;
cp->gss_tokenlen = 0;
return (EAUTH);
}
static uint32_t smb_gss_vc_caps(struct smb_vc *vcp)
{
return (smb_vc_caps(vcp) | SMB_CAP_EXT_SECURITY);
}
static int
smb_gss_ssandx(struct smb_vc *vcp, vfs_context_t context, uint32_t caps, u_int16_t *action)
{
struct smb_rq *rqp = NULL;
struct smb_gss *gp = &vcp->vc_gss;
struct mbchain *mbp;
struct mdchain *mdp;
uint8_t wc;
uint16_t bc, toklen;
int error;
uint32_t tokenlen;
uint32_t maxtokenlen;
uint8_t * tokenptr;
#ifdef SMB_DEBUG
maxtokenlen = 2048;
#else // SMB_DEBUG
maxtokenlen = vcp->vc_sopt.sv_maxtx - (SMB_HDRLEN + SMB_SETUPXRLEN);
#endif // SMB_DEBUG
tokenptr = gp->gss_token;
do {
if (rqp)
smb_rq_done(rqp);
error = smb_rq_alloc(VCTOCP(vcp), SMB_COM_SESSION_SETUP_ANDX, context, &rqp);
if (error)
break;
smb_rq_wstart(rqp);
mbp = &rqp->sr_rq;
mb_put_uint8(mbp, 0xff);
mb_put_uint8(mbp, 0);
mb_put_uint16le(mbp, 0);
mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxtx);
mb_put_uint16le(mbp, vcp->vc_sopt.sv_maxmux);
mb_put_uint16le(mbp, vcp->vc_number);
mb_put_uint32le(mbp, vcp->vc_sopt.sv_skey);
tokenlen = (gp->gss_tokenlen > maxtokenlen) ? maxtokenlen : gp->gss_tokenlen;
mb_put_uint16le(mbp, tokenlen);
mb_put_uint32le(mbp, 0);
mb_put_uint32le(mbp, caps);
smb_rq_wend(rqp);
smb_rq_bstart(rqp);
mb_put_mem(mbp, (caddr_t) tokenptr, tokenlen, MB_MSYSTEM);
smb_put_dstring(mbp, vcp, SMBFS_NATIVEOS, sizeof(SMBFS_NATIVEOS), NO_SFM_CONVERSIONS);
smb_put_dstring(mbp, vcp, SMBFS_LANMAN, sizeof(SMBFS_LANMAN), NO_SFM_CONVERSIONS);
smb_rq_bend(rqp);
error = smb_rq_simple_timed(rqp, SMBSSNSETUPTIMO);
tokenptr += tokenlen;
gp->gss_tokenlen -= tokenlen;
if ((error == EAGAIN) && (vcp->vc_smbuid == 0))
vcp->vc_smbuid = rqp->sr_rpuid;
} while (gp->gss_tokenlen && (error == EAGAIN));
free(gp->gss_token, M_TEMP);
gp->gss_token = NULL;
gp->gss_tokenlen = 0;
gp->gss_smb_error = error;
if (error && (error != EAGAIN)) {
SMBWARNING("Extended security authorization failed! %d\n", error);
goto bad;
}
if (error == EAGAIN) {
error = 0;
}
vcp->vc_smbuid = rqp->sr_rpuid;
smb_rq_getreply(rqp, &mdp);
error = md_get_uint8(mdp, &wc);
if (error)
goto bad;
if (wc != 4) {
error = EBADRPC;
goto bad;
}
md_get_uint8(mdp, NULL);
md_get_uint8(mdp, NULL);
md_get_uint16le(mdp, NULL);
md_get_uint16le(mdp, action);
md_get_uint16le(mdp, &toklen);
gp->gss_tokenlen = toklen;
md_get_uint16le(mdp, &bc);
gp->gss_token = malloc(gp->gss_tokenlen, M_SMBTEMP, M_WAITOK);
error = md_get_mem(mdp, (caddr_t) gp->gss_token, gp->gss_tokenlen, MB_MSYSTEM);
if (error)
goto bad;
bc = (toklen > bc) ? 0 : bc - toklen;
if ((vcp->vc_hflags2 & SMB_FLAGS2_UNICODE) && (bc > 0) && (!(toklen & 1))) {
md_get_uint8(mdp, NULL);
bc -= 1;
}
parse_server_os_lanman_strings(vcp, mdp, bc);
bad:
smb_rq_done(rqp);
return (error);
}
int
smb_gss_ssnsetup(struct smb_vc *vcp, vfs_context_t context)
{
int error = 0;
uint32_t caps;
u_int16_t action = 0;
if (!SMB_USE_GSS(vcp)) {
SMBERROR("Doing Extended Security, but we don't have a gssd port!\n");
return (EINVAL);
}
vcp->vc_smbuid = 0;
caps = smb_gss_vc_caps(vcp);
do {
if (vcp->vc_flags & SMBV_KERBEROS_ACCESS)
error = smb_gss_init(vcp, vcp->vc_uid);
else
error = smb_gss_init_ntlmssp(vcp);
if (error || (SMB_GSS_ERROR(&vcp->vc_gss))) {
SMBWARNING("%s extended security error = %d SMB_GSS_ERROR = %d\n",
(vcp->vc_flags & SMBV_KERBEROS_ACCESS) ? "Kerberos" : "NTLMSSP",
error, SMB_GSS_ERROR(&vcp->vc_gss));
if (error != EAGAIN)
error = EAUTH;
break;
}
if ((vcp->vc_gss.gss_tokenlen) && ((error = smb_gss_ssandx(vcp, context, caps, &action))))
break;
} while (SMB_GSS_CONTINUE_NEEDED(&vcp->vc_gss));
if ((error == 0) && ((vcp->vc_flags & SMBV_GUEST_ACCESS) != SMBV_GUEST_ACCESS) && (action & SMB_ACT_GUEST)) {
if (vcp->vc_pass && vcp->vc_pass[0]) {
SMBWARNING("Got guest access, but wanted real access, logging off.\n");
(void)smb_smb_ssnclose(vcp, context);
error = EAUTH;
} else {
SMBWARNING("Wanted real access, but got guest access because we had no password.\n");
}
}
if (error)
smb_reset_sig(vcp);
smb_gss_reset(&vcp->vc_gss);
return error;
}