/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ /* * SPNEGO back-end for NTLMSSP. See [MS-NLMP] */ #include #include #include #include "smbd.h" #include "smbd_authsvc.h" #include "netsmb/ntlmssp.h" #include /* A shorter alias for a crazy long name from [MS-NLMP] */ #define NTLMSSP_NEGOTIATE_NTLM2 \ NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY /* Need this in a header somewhere */ #ifdef _LITTLE_ENDIAN /* little-endian values on little-endian */ #define htolel(x) ((uint32_t)(x)) #define letohl(x) ((uint32_t)(x)) #else /* (BYTE_ORDER == LITTLE_ENDIAN) */ /* little-endian values on big-endian (swap) */ #define letohl(x) BSWAP_32(x) #define htolel(x) BSWAP_32(x) #endif /* (BYTE_ORDER == LITTLE_ENDIAN) */ 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]; /* "NTLMSSP" */ 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; /* workstation domain, name (place holders) */ 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; /* encrypted session key */ uint32_t h_flags; /* Version struct (optional) */ /* MIC hash (optional) */ }; /* Allow turning these off for debugging, etc. */ 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 **); /* * Initialize this context for NTLMSSP, if possible. */ 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); } /* * Handle an auth message */ 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: /* Sent by servers, not received. */ rc = NT_STATUS_INVALID_PARAMETER; break; } return (rc); } #if (MAXHOSTNAMELEN < NETBIOS_NAME_SZ) #error "MAXHOSTNAMELEN < NETBIOS_NAME_SZ" #endif /* * Handle an NTLMSSP_MSGTYPE_NEGOTIATE message, and reply * with an NTLMSSP_MSGTYPE_CHALLENGE message. * See: [MS-NLMP] 2.2.1.1, 3.2.5.1.1 */ 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); /* * Looks like we can ignore ws_dom, ws_name. * Otherwise would parse those here. */ 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)); } /* * Compute srv_flags */ 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; /* LM Key is mutually exclusive with NTLM2 */ if ((be->srv_flags & NTLMSSP_NEGOTIATE_NTLM2) == 0 && (be->clnt_flags & NTLMSSP_NEGOTIATE_LM_KEY) != 0) be->srv_flags |= NTLMSSP_NEGOTIATE_LM_KEY; /* Get our "target name" */ 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; /* * Build the NTLMSSP_MSGTYPE_CHALLENGE message. */ 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); /* * Fixed size parts */ rc = smb_msgbuf_encode( &mb, "8clwwll8cllwwl", /* offset, name (fmt) */ "NTLMSSP", /* 0: signature (8c) */ NTLMSSP_MSGTYPE_CHALLENGE, /* 8: type (l) */ 0, 0, 0, /* filled later: 12: target name (wwl) */ be->srv_flags, /* 20: flags (l) */ be->srv_challenge, /* 24: (8c) */ 0, 0, /* 32: reserved (ll) */ 0, 0, 0); /* filled later: 40: target info (wwl) */ #define TARGET_NAME_OFFSET 12 #define TARGET_INFO_OFFSET 40 if (rc < 0) goto errout; /* * Variable length parts. * * Target name */ 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; /* overwrite target name offset+lengths */ 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; /* * Target info (AvPairList) * * These AV pairs are like our name/value pairs, but have * numeric identifiers instead of names. There are many * of these, but we put only the four expected by Windows: * NetBIOS computer name * NetBIOS domain name * DNS computer name * DNS domain name * Note that "domain" above (even "DNS domain") refers to * the AD domain of which we're a member, which may be * _different_ from the configured DNS domain. * * Also note that in "workgroup" mode (not a domain member) * all "domain" fields should be set to the same values as * the "computer" fields ("bare" host name, not FQDN). */ var_start = smb_msgbuf_used(&mb); /* NetBIOS Computer Name */ 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) { /* * Workgroup mode. Set all to hostname. * tmp_name = netbios hostname from above. */ if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0) goto errout; /* * Want the bare computer name here (not FQDN). */ 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 { /* * Domain mode. Use real host and domain values. */ /* NetBIOS Domain Name */ if (smb_getdomainname(tmp_name, NETBIOS_NAME_SZ)) goto errout; if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0) goto errout; /* DNS Computer Name */ if (smb_getfqhostname(tmp_name, MAXHOSTNAMELEN)) goto errout; if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0) goto errout; /* DNS Domain Name */ if (smb_getfqdomainname(tmp_name, MAXHOSTNAMELEN)) goto errout; if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0) goto errout; } /* End marker */ 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); /* overwrite target offset+lengths */ 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); } /* * Handle an NTLMSSP_MSGTYPE_AUTHENTICATE message. * See: [MS-NLMP] 2.2.1.3, 3.2.5.1.2 */ 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; /* encrypted session key (optional) */ 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)); /* * Parse the NTLMSSP_MSGTYPE_AUTHENTICATE message. */ 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 we're doing extended session security, the challenge * this OWF was computed with is different. [MS-NLMP 3.3.1] * It's: MD5(concat(ServerChallenge,ClientChallenge)) * where the ClientChallenge is in the LM resp. field. */ 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; } /* * This (indirectly) calls smb_auth_validate() to * check that the client gave us a valid hash. */ token = smbd_user_auth_logon(&user_info); if (token == NULL) { status = user_info.lg_status; if (status == 0) /* should not happen */ status = NT_STATUS_INTERNAL_ERROR; goto errout; } if (token->tkn_ssnkey.val != NULL && token->tkn_ssnkey.len == SMBAUTH_HASH_SZ) { /* * At this point, token->tkn_session_key is the * "Session Base Key" [MS-NLMP] 3.2.5.1.2 * Compute the final session key. First need the * "Key Exchange Key" [MS-NLMP] 3.4.5.1 */ if (ntlm_v1x) { smb_auth_ntlm2_kxkey(kxkey, be->srv_challenge, lm_resp, token->tkn_ssnkey.val); } else { /* KXKEY is the Session Base Key. */ (void) memcpy(kxkey, token->tkn_ssnkey.val, SMBAUTH_HASH_SZ); } /* * If the client give us an encrypted session key, * decrypt it (RC4) using the "key exchange key". */ if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { /* RC4 args: result, key, data */ (void) smb_auth_RC4(token->tkn_ssnkey.val, SMBAUTH_HASH_SZ, kxkey, SMBAUTH_HASH_SZ, essn_key, hdr.h_essn_key.sb_length); } else { /* Final key is the KXKEY */ (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); }