#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <netdb.h>
#include <libintl.h>
#include <xti.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/byteorder.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netsmb/smb.h>
#include <netsmb/smb_lib.h>
#include <netsmb/mchain.h>
#include "private.h"
#include "charsets.h"
#include "smb_crypt.h"
#include "spnego.h"
#include "derparse.h"
#include "ssp.h"
#include "ntlm.h"
#include "ntlmssp.h"
#define NTLMSSP_NEGOTIATE_ESS \
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
typedef struct ntlmssp_state {
uint32_t ss_flags;
char *ss_target_name;
struct mbuf *ss_target_info;
uchar_t ss_ssnkey[NTLM_HASH_SZ];
uchar_t ss_kxkey[NTLM_HASH_SZ];
} ntlmssp_state_t;
struct sec_buf {
uint16_t sb_length;
uint16_t sb_maxlen;
uint32_t sb_offset;
};
#define ID_SZ 8
static const char ntlmssp_id[ID_SZ] = "NTLMSSP";
static int
ntlm_rand_ssn_key(ntlmssp_state_t *ssp_st, struct mbdata *ek_mbp);
static int
md_get_sb_hdr(struct mbdata *mbp, struct sec_buf *sb)
{
int err;
(void) md_get_uint16le(mbp, &sb->sb_length);
(void) md_get_uint16le(mbp, &sb->sb_maxlen);
err = md_get_uint32le(mbp, &sb->sb_offset);
return (err);
}
static int
md_get_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf **mp)
{
struct mbdata tmp_mb;
int err;
mb_initm(&tmp_mb, mbp->mb_top);
err = md_get_mem(&tmp_mb, NULL, sb->sb_offset, MB_MSYSTEM);
if (err)
return (err);
err = md_get_mbuf(&tmp_mb, sb->sb_maxlen, mp);
return (err);
}
static int
mb_put_sb_hdr(struct mbdata *mbp, struct sec_buf *sb)
{
int err;
(void) mb_put_uint16le(mbp, sb->sb_length);
(void) mb_put_uint16le(mbp, sb->sb_maxlen);
err = mb_put_uint32le(mbp, sb->sb_offset);
return (err);
}
static int
mb_put_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf *m)
{
int cnt0;
int err = 0;
sb->sb_offset = cnt0 = mbp->mb_count;
if (m != NULL)
err = mb_put_mbuf(mbp, m);
sb->sb_maxlen = sb->sb_length = mbp->mb_count - cnt0;
return (err);
}
static int
mb_put_sb_string(struct mbdata *mbp, struct sec_buf *sb,
const char *str, int unicode)
{
int err, trim;
struct mbdata tmp_mb;
bzero(&tmp_mb, sizeof (tmp_mb));
if (str != NULL && *str != '\0') {
err = mb_init(&tmp_mb);
if (err)
return (err);
err = mb_put_string(&tmp_mb, str, unicode);
if (err)
return (err);
trim = (unicode) ? 2 : 1;
if (tmp_mb.mb_cur->m_len < trim)
trim = 0;
tmp_mb.mb_cur->m_len -= trim;
}
err = mb_put_sb_data(mbp, sb, tmp_mb.mb_top);
return (err);
}
int
ntlmssp_put_type1(struct ssp_ctx *sp, struct mbdata *out_mb)
{
struct type1hdr {
char h_id[ID_SZ];
uint32_t h_type;
uint32_t h_flags;
struct sec_buf h_cldom;
struct sec_buf h_wksta;
} hdr;
struct mbdata mb2;
int err;
struct smb_ctx *ctx = sp->smb_ctx;
ntlmssp_state_t *ssp_st = sp->sp_private;
if ((err = mb_init(&mb2)) != 0)
return (err);
mb2.mb_count = sizeof (hdr);
ssp_st->ss_flags =
NTLMSSP_NEGOTIATE_UNICODE |
NTLMSSP_NEGOTIATE_OEM |
NTLMSSP_REQUEST_TARGET |
NTLMSSP_NEGOTIATE_SIGN |
NTLMSSP_NEGOTIATE_SEAL |
NTLMSSP_NEGOTIATE_NTLM |
NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
NTLMSSP_NEGOTIATE_ESS |
NTLMSSP_NEGOTIATE_128 |
NTLMSSP_NEGOTIATE_KEY_EXCH |
NTLMSSP_NEGOTIATE_56;
if ((ctx->ct_vopt & SMBVOPT_SIGNING_ENABLED) == 0)
ssp_st->ss_flags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
bcopy(ntlmssp_id, &hdr.h_id, ID_SZ);
hdr.h_type = NTLMSSP_MSGTYPE_NEGOTIATE;
hdr.h_flags = ssp_st->ss_flags;
(void) mb_put_sb_string(&mb2, &hdr.h_cldom, NULL, 0);
(void) mb_put_sb_string(&mb2, &hdr.h_wksta, NULL, 0);
(void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
(void) mb_put_uint32le(out_mb, hdr.h_type);
(void) mb_put_uint32le(out_mb, hdr.h_flags);
(void) mb_put_sb_hdr(out_mb, &hdr.h_cldom);
(void) mb_put_sb_hdr(out_mb, &hdr.h_wksta);
err = mb_put_mbuf(out_mb, mb2.mb_top);
return (err);
}
int
ntlmssp_get_type2(struct ssp_ctx *sp, struct mbdata *in_mb)
{
struct type2hdr {
char h_id[ID_SZ];
uint32_t h_type;
struct sec_buf h_target_name;
uint32_t h_flags;
uint8_t h_challenge[8];
uint32_t h_context[2];
struct sec_buf h_target_info;
} hdr;
struct mbdata top_mb, tmp_mb;
struct mbuf *m;
int err, uc;
int min_hdr_sz = offsetof(struct type2hdr, h_context);
struct smb_ctx *ctx = sp->smb_ctx;
ntlmssp_state_t *ssp_st = sp->sp_private;
char *buf = NULL;
if (m_totlen(in_mb->mb_top) < min_hdr_sz) {
err = EBADRPC;
goto out;
}
top_mb = *in_mb;
bzero(&hdr, sizeof (hdr));
(void) md_get_mem(in_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
(void) md_get_uint32le(in_mb, &hdr.h_type);
if (hdr.h_type != NTLMSSP_MSGTYPE_CHALLENGE) {
err = EPROTO;
goto out;
}
(void) md_get_sb_hdr(in_mb, &hdr.h_target_name);
(void) md_get_uint32le(in_mb, &hdr.h_flags);
(void) md_get_mem(in_mb, &hdr.h_challenge, NTLM_CHAL_SZ, MB_MSYSTEM);
ssp_st->ss_flags = hdr.h_flags;
bcopy(&hdr.h_challenge, ctx->ct_srv_chal, NTLM_CHAL_SZ);
uc = hdr.h_flags & NTLMSSP_NEGOTIATE_UNICODE;
ssp_st->ss_flags &= ~NTLMSSP_NEGOTIATE_VERSION;
if ((m_totlen(top_mb.mb_top) > sizeof (hdr)) &&
(hdr.h_target_name.sb_offset >= sizeof (hdr))) {
(void) md_get_uint32le(in_mb, &hdr.h_context[0]);
(void) md_get_uint32le(in_mb, &hdr.h_context[1]);
(void) md_get_sb_hdr(in_mb, &hdr.h_target_info);
}
err = md_get_sb_data(&top_mb, &hdr.h_target_name, &m);
if (err)
goto out;
mb_initm(&tmp_mb, m);
err = md_get_string(&tmp_mb, &ssp_st->ss_target_name, uc);
mb_done(&tmp_mb);
if (hdr.h_target_info.sb_offset >= sizeof (hdr)) {
err = md_get_sb_data(&top_mb, &hdr.h_target_info,
&ssp_st->ss_target_info);
}
out:
if (buf != NULL)
free(buf);
return (err);
}
int
ntlmssp_put_type3(struct ssp_ctx *sp, struct mbdata *out_mb)
{
struct type3hdr {
char h_id[ID_SZ];
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_ssn_key;
uint32_t h_flags;
uchar_t h_mic[NTLM_HASH_SZ];
} hdr;
struct mbdata lm_mbc;
struct mbdata nt_mbc;
struct mbdata ti_mbc;
struct mbdata ek_mbc;
struct mbdata mb2;
int err, uc;
struct smb_ctx *ctx = sp->smb_ctx;
ntlmssp_state_t *ssp_st = sp->sp_private;
uchar_t *pmic;
bzero(&hdr, sizeof (hdr));
bzero(&lm_mbc, sizeof (lm_mbc));
bzero(&nt_mbc, sizeof (nt_mbc));
bzero(&ti_mbc, sizeof (ti_mbc));
bzero(&ek_mbc, sizeof (ek_mbc));
bzero(&mb2, sizeof (mb2));
if ((err = mb_init(&mb2)) != 0)
goto out;
mb2.mb_count = sizeof (hdr);
uc = ssp_st->ss_flags & NTLMSSP_NEGOTIATE_UNICODE;
bcopy(ntlmssp_id, &hdr.h_id, ID_SZ);
hdr.h_type = NTLMSSP_MSGTYPE_AUTHENTICATE;
hdr.h_flags = ssp_st->ss_flags;
if (ctx->ct_authflags & SMB_AT_ANON) {
ssp_st->ss_flags |= NTLMSSP_NEGOTIATE_NULL_SESSION;
ssp_st->ss_flags &= ~NTLMSSP_NEGOTIATE_ESS;
hdr.h_flags = ssp_st->ss_flags;
err = 0;
memset(ssp_st->ss_ssnkey, 0, NTLM_HASH_SZ);
memset(ssp_st->ss_kxkey, 0, NTLM_HASH_SZ);
} else if (ctx->ct_authflags & SMB_AT_NTLM2) {
err = ntlm_build_target_info(ctx,
ssp_st->ss_target_info, &ti_mbc);
if (err)
goto out;
err = ntlm_put_v2_responses(ctx, &ti_mbc,
&lm_mbc, &nt_mbc, ssp_st->ss_ssnkey);
if (err)
goto out;
memcpy(ssp_st->ss_kxkey, ssp_st->ss_ssnkey, NTLM_HASH_SZ);
} else if (ssp_st->ss_flags & NTLMSSP_NEGOTIATE_ESS) {
err = ntlm_put_v1x_responses(ctx,
&lm_mbc, &nt_mbc, ssp_st->ss_ssnkey);
if (err)
goto out;
ntlm2_kxkey(ctx, &lm_mbc, ssp_st->ss_ssnkey,
ssp_st->ss_kxkey);
} else {
err = ntlm_put_v1_responses(ctx,
&lm_mbc, &nt_mbc, ssp_st->ss_ssnkey);
if (err)
goto out;
memcpy(ssp_st->ss_kxkey, ssp_st->ss_ssnkey, NTLM_HASH_SZ);
}
if (ssp_st->ss_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
err = ntlm_rand_ssn_key(ssp_st, &ek_mbc);
if (err)
goto out;
} else {
memcpy(ssp_st->ss_ssnkey, ssp_st->ss_kxkey, NTLM_HASH_SZ);
}
err = mb_put_sb_data(&mb2, &hdr.h_lm_resp, lm_mbc.mb_top);
lm_mbc.mb_top = NULL;
if (err)
goto out;
err = mb_put_sb_data(&mb2, &hdr.h_nt_resp, nt_mbc.mb_top);
nt_mbc.mb_top = NULL;
if (err)
goto out;
err = mb_put_sb_string(&mb2, &hdr.h_domain, ctx->ct_domain, uc);
if (err)
goto out;
err = mb_put_sb_string(&mb2, &hdr.h_user, ctx->ct_user, uc);
if (err)
goto out;
err = mb_put_sb_string(&mb2, &hdr.h_wksta, ctx->ct_locname, uc);
if (err)
goto out;
err = mb_put_sb_data(&mb2, &hdr.h_ssn_key, ek_mbc.mb_top);
ek_mbc.mb_top = NULL;
if (err)
goto out;
(void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
(void) mb_put_uint32le(out_mb, hdr.h_type);
(void) mb_put_sb_hdr(out_mb, &hdr.h_lm_resp);
(void) mb_put_sb_hdr(out_mb, &hdr.h_nt_resp);
(void) mb_put_sb_hdr(out_mb, &hdr.h_domain);
(void) mb_put_sb_hdr(out_mb, &hdr.h_user);
(void) mb_put_sb_hdr(out_mb, &hdr.h_wksta);
(void) mb_put_sb_hdr(out_mb, &hdr.h_ssn_key);
(void) mb_put_uint32le(out_mb, hdr.h_flags);
pmic = mb_reserve(out_mb, NTLM_HASH_SZ);
err = mb_put_mbuf(out_mb, mb2.mb_top);
mb2.mb_top = NULL;
(void) pmic;
out:
mb_done(&mb2);
mb_done(&lm_mbc);
mb_done(&nt_mbc);
mb_done(&ti_mbc);
mb_done(&ek_mbc);
return (err);
}
static int
ntlm_rand_ssn_key(
ntlmssp_state_t *ssp_st,
struct mbdata *ek_mbp)
{
uchar_t *encr_ssn_key;
int err;
if ((err = mb_init(ek_mbp)) != 0)
return (err);
encr_ssn_key = mb_reserve(ek_mbp, NTLM_HASH_SZ);
(void) smb_get_urandom(ssp_st->ss_ssnkey, NTLM_HASH_SZ);
err = smb_encrypt_RC4(encr_ssn_key, NTLM_HASH_SZ,
ssp_st->ss_kxkey, NTLM_HASH_SZ,
ssp_st->ss_ssnkey, NTLM_HASH_SZ);
return (err);
}
int
ntlmssp_final(struct ssp_ctx *sp)
{
struct smb_ctx *ctx = sp->smb_ctx;
ntlmssp_state_t *ssp_st = sp->sp_private;
int err = 0;
if (ctx->ct_ssnkey_buf != NULL) {
free(ctx->ct_ssnkey_buf);
ctx->ct_ssnkey_buf = NULL;
}
ctx->ct_ssnkey_buf = malloc(NTLM_HASH_SZ);
if (ctx->ct_ssnkey_buf == NULL) {
err = ENOMEM;
goto out;
}
ctx->ct_ssnkey_len = NTLM_HASH_SZ;
memcpy(ctx->ct_ssnkey_buf, ssp_st->ss_ssnkey, NTLM_HASH_SZ);
out:
return (err);
}
int
ntlmssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
struct mbdata *out_mb)
{
int err;
if (out_mb == NULL) {
err = ntlmssp_final(sp);
goto out;
}
err = mb_init(out_mb);
if (err)
goto out;
if (in_mb == NULL) {
err = ntlmssp_put_type1(sp, out_mb);
goto out;
}
err = ntlmssp_get_type2(sp, in_mb);
if (err)
goto out;
err = ntlmssp_put_type3(sp, out_mb);
out:
if (err)
DPRINT("ret: %d", err);
return (err);
}
void
ntlmssp_destroy(struct ssp_ctx *sp)
{
ntlmssp_state_t *ssp_st;
ssp_st = sp->sp_private;
if (ssp_st != NULL) {
sp->sp_private = NULL;
free(ssp_st->ss_target_name);
m_freem(ssp_st->ss_target_info);
free(ssp_st);
}
}
int
ntlmssp_init_client(struct ssp_ctx *sp)
{
ntlmssp_state_t *ssp_st;
smb_ctx_t *ctx = sp->smb_ctx;
if ((ctx->ct_authflags &
(SMB_AT_NTLM2 | SMB_AT_NTLM1 | SMB_AT_ANON)) == 0) {
DPRINT("No NTLM authflags");
return (EINVAL);
}
(void) smb_get_urandom(ctx->ct_clnonce, NTLM_CHAL_SZ);
ssp_st = calloc(1, sizeof (*ssp_st));
if (ssp_st == NULL)
return (ENOMEM);
sp->sp_nexttok = ntlmssp_next_token;
sp->sp_destroy = ntlmssp_destroy;
sp->sp_private = ssp_st;
return (0);
}