#include <sys/types.h>
#include <sys/byteorder.h>
#include <strings.h>
#include "smbd.h"
#include "smbd_authsvc.h"
#include "netsmb/ntlmssp.h"
#include <assert.h>
#define NTLMSSP_NEGOTIATE_NTLM2 \
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
#ifdef _LITTLE_ENDIAN
#define htolel(x) ((uint32_t)(x))
#define letohl(x) ((uint32_t)(x))
#else
#define letohl(x) BSWAP_32(x)
#define htolel(x) BSWAP_32(x)
#endif
typedef struct ntlmssp_backend {
uint32_t expect_type;
uint32_t clnt_flags;
uint32_t srv_flags;
char srv_challenge[8];
} ntlmssp_backend_t;
struct genhdr {
char h_id[8];
uint32_t h_type;
};
struct sec_buf {
uint16_t sb_length;
uint16_t sb_maxlen;
uint32_t sb_offset;
};
struct nego_hdr {
char h_id[8];
uint32_t h_type;
uint32_t h_flags;
uint16_t ws_dom[4];
uint16_t ws_name[4];
};
struct auth_hdr {
char h_id[8];
uint32_t h_type;
struct sec_buf h_lm_resp;
struct sec_buf h_nt_resp;
struct sec_buf h_domain;
struct sec_buf h_user;
struct sec_buf h_wksta;
struct sec_buf h_essn_key;
uint32_t h_flags;
};
int smbd_signing_enabled = 1;
int smbd_constant_challenge = 0;
static uint8_t constant_chal[8] = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
static int smbd_ntlmssp_negotiate(authsvc_context_t *);
static int smbd_ntlmssp_authenticate(authsvc_context_t *);
static int encode_avpair_str(smb_msgbuf_t *, uint16_t, char *);
static int decode_secbuf_bin(smb_msgbuf_t *, struct sec_buf *, void **);
static int decode_secbuf_str(smb_msgbuf_t *, struct sec_buf *, char **);
int
smbd_ntlmssp_init(authsvc_context_t *ctx)
{
ntlmssp_backend_t *be;
be = malloc(sizeof (*be));
if (be == 0)
return (NT_STATUS_NO_MEMORY);
bzero(be, sizeof (*be));
be->expect_type = NTLMSSP_MSGTYPE_NEGOTIATE;
ctx->ctx_backend = be;
return (0);
}
void
smbd_ntlmssp_fini(authsvc_context_t *ctx)
{
free(ctx->ctx_backend);
}
int
smbd_ntlmssp_work(authsvc_context_t *ctx)
{
struct genhdr *ihdr = ctx->ctx_ibodybuf;
ntlmssp_backend_t *be = ctx->ctx_backend;
uint32_t mtype;
int rc;
if (ctx->ctx_ibodylen < sizeof (*ihdr))
return (NT_STATUS_INVALID_PARAMETER);
if (bcmp(ihdr->h_id, "NTLMSSP", 8))
return (NT_STATUS_INVALID_PARAMETER);
mtype = letohl(ihdr->h_type);
if (mtype != be->expect_type)
return (NT_STATUS_INVALID_PARAMETER);
switch (mtype) {
case NTLMSSP_MSGTYPE_NEGOTIATE:
ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
rc = smbd_ntlmssp_negotiate(ctx);
break;
case NTLMSSP_MSGTYPE_AUTHENTICATE:
ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
rc = smbd_ntlmssp_authenticate(ctx);
break;
default:
case NTLMSSP_MSGTYPE_CHALLENGE:
rc = NT_STATUS_INVALID_PARAMETER;
break;
}
return (rc);
}
#if (MAXHOSTNAMELEN < NETBIOS_NAME_SZ)
#error "MAXHOSTNAMELEN < NETBIOS_NAME_SZ"
#endif
static int
smbd_ntlmssp_negotiate(authsvc_context_t *ctx)
{
char tmp_name[MAXHOSTNAMELEN];
ntlmssp_backend_t *be = ctx->ctx_backend;
struct nego_hdr *ihdr = ctx->ctx_ibodybuf;
smb_msgbuf_t mb;
uint8_t *save_scan;
int secmode;
int mbflags;
int rc;
size_t var_start, var_end;
uint16_t var_size;
if (ctx->ctx_ibodylen < sizeof (*ihdr))
return (NT_STATUS_INVALID_PARAMETER);
be->clnt_flags = letohl(ihdr->h_flags);
secmode = smb_config_get_secmode();
if (smbd_constant_challenge) {
(void) memcpy(be->srv_challenge, constant_chal,
sizeof (be->srv_challenge));
} else {
randomize(be->srv_challenge, sizeof (be->srv_challenge));
}
be->srv_flags =
NTLMSSP_REQUEST_TARGET |
NTLMSSP_NEGOTIATE_NTLM |
NTLMSSP_NEGOTIATE_TARGET_INFO;
be->srv_flags |= be->clnt_flags & (
NTLMSSP_NEGOTIATE_NTLM2 |
NTLMSSP_NEGOTIATE_128 |
NTLMSSP_NEGOTIATE_KEY_EXCH |
NTLMSSP_NEGOTIATE_56);
if (smbd_signing_enabled) {
be->srv_flags |= be->clnt_flags & (
NTLMSSP_NEGOTIATE_SIGN |
NTLMSSP_NEGOTIATE_SEAL |
NTLMSSP_NEGOTIATE_ALWAYS_SIGN);
}
if (be->clnt_flags & NTLMSSP_NEGOTIATE_UNICODE)
be->srv_flags |= NTLMSSP_NEGOTIATE_UNICODE;
else if (be->clnt_flags & NTLMSSP_NEGOTIATE_OEM)
be->srv_flags |= NTLMSSP_NEGOTIATE_OEM;
if ((be->srv_flags & NTLMSSP_NEGOTIATE_NTLM2) == 0 &&
(be->clnt_flags & NTLMSSP_NEGOTIATE_LM_KEY) != 0)
be->srv_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
if (secmode == SMB_SECMODE_DOMAIN) {
be->srv_flags |= NTLMSSP_TARGET_TYPE_DOMAIN;
rc = smb_getdomainname(tmp_name, NETBIOS_NAME_SZ);
} else {
be->srv_flags |= NTLMSSP_TARGET_TYPE_SERVER;
rc = smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ);
}
if (rc)
goto errout;
mbflags = SMB_MSGBUF_NOTERM;
if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
mbflags |= SMB_MSGBUF_UNICODE;
smb_msgbuf_init(&mb, ctx->ctx_obodybuf, ctx->ctx_obodylen, mbflags);
rc = smb_msgbuf_encode(
&mb, "8clwwll8cllwwl",
"NTLMSSP",
NTLMSSP_MSGTYPE_CHALLENGE,
0, 0, 0,
be->srv_flags,
be->srv_challenge,
0, 0,
0, 0, 0);
#define TARGET_NAME_OFFSET 12
#define TARGET_INFO_OFFSET 40
if (rc < 0)
goto errout;
var_start = smb_msgbuf_used(&mb);
rc = smb_msgbuf_encode(&mb, "u", tmp_name);
var_end = smb_msgbuf_used(&mb);
var_size = (uint16_t)(var_end - var_start);
if (rc < 0)
goto errout;
save_scan = mb.scan;
mb.scan = mb.base + TARGET_NAME_OFFSET;
(void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
mb.scan = save_scan;
var_start = smb_msgbuf_used(&mb);
if (smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ))
goto errout;
if (encode_avpair_str(&mb, MsvAvNbComputerName, tmp_name) < 0)
goto errout;
if (secmode != SMB_SECMODE_DOMAIN) {
if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
goto errout;
if (smb_gethostname(tmp_name, MAXHOSTNAMELEN, SMB_CASE_LOWER))
goto errout;
if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
goto errout;
if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
goto errout;
} else {
if (smb_getdomainname(tmp_name, NETBIOS_NAME_SZ))
goto errout;
if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
goto errout;
if (smb_getfqhostname(tmp_name, MAXHOSTNAMELEN))
goto errout;
if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
goto errout;
if (smb_getfqdomainname(tmp_name, MAXHOSTNAMELEN))
goto errout;
if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
goto errout;
}
if (smb_msgbuf_encode(&mb, "ww", MsvAvEOL, 0) < 0)
goto errout;
var_end = smb_msgbuf_used(&mb);
var_size = (uint16_t)(var_end - var_start);
save_scan = mb.scan;
mb.scan = mb.base + TARGET_INFO_OFFSET;
(void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
mb.scan = save_scan;
ctx->ctx_obodylen = smb_msgbuf_used(&mb);
smb_msgbuf_term(&mb);
be->expect_type = NTLMSSP_MSGTYPE_AUTHENTICATE;
return (0);
errout:
smb_msgbuf_term(&mb);
return (NT_STATUS_INTERNAL_ERROR);
}
static int
encode_avpair_str(smb_msgbuf_t *mb, uint16_t AvId, char *name)
{
int rc;
uint16_t len;
len = smb_wcequiv_strlen(name);
rc = smb_msgbuf_encode(mb, "wwU", AvId, len, name);
return (rc);
}
static int
smbd_ntlmssp_authenticate(authsvc_context_t *ctx)
{
struct auth_hdr hdr;
smb_msgbuf_t mb;
smb_logon_t user_info;
smb_token_t *token = NULL;
ntlmssp_backend_t *be = ctx->ctx_backend;
void *lm_resp;
void *nt_resp;
char *domain;
char *user;
char *wksta;
void *essn_key;
int mbflags;
uint_t status = NT_STATUS_INTERNAL_ERROR;
char combined_challenge[SMBAUTH_CHAL_SZ];
unsigned char kxkey[SMBAUTH_HASH_SZ];
boolean_t ntlm_v1x = B_FALSE;
bzero(&user_info, sizeof (user_info));
if (ctx->ctx_ibodylen < sizeof (hdr))
return (NT_STATUS_INVALID_PARAMETER);
mbflags = SMB_MSGBUF_NOTERM;
if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
mbflags |= SMB_MSGBUF_UNICODE;
smb_msgbuf_init(&mb, ctx->ctx_ibodybuf, ctx->ctx_ibodylen, mbflags);
bzero(&hdr, sizeof (hdr));
if (smb_msgbuf_decode(&mb, "12.") < 0)
goto errout;
if (decode_secbuf_bin(&mb, &hdr.h_lm_resp, &lm_resp) < 0)
goto errout;
if (decode_secbuf_bin(&mb, &hdr.h_nt_resp, &nt_resp) < 0)
goto errout;
if (decode_secbuf_str(&mb, &hdr.h_domain, &domain) < 0)
goto errout;
if (decode_secbuf_str(&mb, &hdr.h_user, &user) < 0)
goto errout;
if (decode_secbuf_str(&mb, &hdr.h_wksta, &wksta) < 0)
goto errout;
if (decode_secbuf_bin(&mb, &hdr.h_essn_key, &essn_key) < 0)
goto errout;
if (smb_msgbuf_decode(&mb, "l", &be->clnt_flags) < 0)
goto errout;
if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
if (hdr.h_essn_key.sb_length < 16 || essn_key == NULL)
goto errout;
}
user_info.lg_level = NETR_NETWORK_LOGON;
user_info.lg_flags = 0;
user_info.lg_ntlm_flags = be->clnt_flags;
user_info.lg_username = (user) ? user : "";
user_info.lg_domain = (domain) ? domain : "";
user_info.lg_workstation = (wksta) ? wksta : "";
user_info.lg_clnt_ipaddr =
ctx->ctx_clinfo.lci_clnt_ipaddr;
user_info.lg_local_port = 445;
user_info.lg_challenge_key.len = SMBAUTH_CHAL_SZ;
user_info.lg_challenge_key.val = (uint8_t *)be->srv_challenge;
user_info.lg_nt_password.len = hdr.h_nt_resp.sb_length;
user_info.lg_nt_password.val = nt_resp;
user_info.lg_lm_password.len = hdr.h_lm_resp.sb_length;
user_info.lg_lm_password.val = lm_resp;
user_info.lg_native_os = ctx->ctx_clinfo.lci_native_os;
user_info.lg_native_lm = ctx->ctx_clinfo.lci_native_lm;
if (user_info.lg_nt_password.len == SMBAUTH_LM_RESP_SZ &&
user_info.lg_lm_password.len >= SMBAUTH_CHAL_SZ &&
(be->clnt_flags & NTLMSSP_NEGOTIATE_NTLM2) != 0) {
smb_auth_ntlm2_mkchallenge(combined_challenge,
be->srv_challenge, lm_resp);
user_info.lg_challenge_key.val =
(uint8_t *)combined_challenge;
user_info.lg_lm_password.len = 0;
ntlm_v1x = B_TRUE;
}
token = smbd_user_auth_logon(&user_info);
if (token == NULL) {
status = user_info.lg_status;
if (status == 0)
status = NT_STATUS_INTERNAL_ERROR;
goto errout;
}
if (token->tkn_ssnkey.val != NULL &&
token->tkn_ssnkey.len == SMBAUTH_HASH_SZ) {
if (ntlm_v1x) {
smb_auth_ntlm2_kxkey(kxkey,
be->srv_challenge, lm_resp,
token->tkn_ssnkey.val);
} else {
(void) memcpy(kxkey, token->tkn_ssnkey.val,
SMBAUTH_HASH_SZ);
}
if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
(void) smb_auth_RC4(token->tkn_ssnkey.val,
SMBAUTH_HASH_SZ, kxkey, SMBAUTH_HASH_SZ,
essn_key, hdr.h_essn_key.sb_length);
} else {
(void) memcpy(token->tkn_ssnkey.val, kxkey,
SMBAUTH_HASH_SZ);
}
}
ctx->ctx_token = token;
ctx->ctx_obodylen = 0;
smb_msgbuf_term(&mb);
return (0);
errout:
smb_msgbuf_term(&mb);
return (status);
}
static int
decode_secbuf_bin(smb_msgbuf_t *mb, struct sec_buf *sb, void **binp)
{
int rc;
*binp = NULL;
rc = smb_msgbuf_decode(
mb, "wwl",
&sb->sb_length,
&sb->sb_maxlen,
&sb->sb_offset);
if (rc < 0)
return (rc);
if (sb->sb_offset > mb->max)
return (SMB_MSGBUF_UNDERFLOW);
if (sb->sb_length > (mb->max - sb->sb_offset))
return (SMB_MSGBUF_UNDERFLOW);
if (sb->sb_length == 0)
return (rc);
*binp = mb->base + sb->sb_offset;
return (0);
}
static int
decode_secbuf_str(smb_msgbuf_t *mb, struct sec_buf *sb, char **cpp)
{
uint8_t *save_scan;
int rc;
*cpp = NULL;
rc = smb_msgbuf_decode(
mb, "wwl",
&sb->sb_length,
&sb->sb_maxlen,
&sb->sb_offset);
if (rc < 0)
return (rc);
if (sb->sb_offset > mb->max)
return (SMB_MSGBUF_UNDERFLOW);
if (sb->sb_length > (mb->max - sb->sb_offset))
return (SMB_MSGBUF_UNDERFLOW);
if (sb->sb_length == 0)
return (rc);
save_scan = mb->scan;
mb->scan = mb->base + sb->sb_offset;
rc = smb_msgbuf_decode(mb, "#u", (int)sb->sb_length, cpp);
mb->scan = save_scan;
return (rc);
}