/* NTLM SASL plugin
* Ken Murchison
* $Id: ntlm.c,v 1.33 2008/10/30 14:19:46 mel Exp $
*
* References:
* http://www.innovation.ch/java/ntlm.html
* http://www.opengroup.org/comsource/techref2/NCH1222X.HTM
* http://www.ubiqx.org/cifs/rfc-draft/draft-leach-cifs-v1-spec-02.html
*/
/*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#ifdef WIN32
# include <process.h> /* for getpid */
typedef int pid_t;
#else
# include <unistd.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/utsname.h>
# include <netdb.h>
#ifndef SYS_NMLN
struct utsname dummy;
# define SYS_NMLN sizeof(dummy.sysname)
#endif
# define closesocket(sock) close(sock)
typedef int SOCKET;
#endif /* WIN32 */
#include <openssl/md4.h>
#include <openssl/md5.h>
#include <openssl/hmac.h>
#include <openssl/des.h>
#include <openssl/opensslv.h>
#if (OPENSSL_VERSION_NUMBER >= 0x0090700f) && \
!defined(OPENSSL_ENABLE_OLD_DES_SUPPORT)
# define des_cblock DES_cblock
# define des_key_schedule DES_key_schedule
# define des_set_odd_parity(k) \
DES_set_odd_parity((k))
# define des_set_key(k,ks) \
DES_set_key((k),&(ks))
# define des_key_sched(k,ks) \
DES_key_sched((k),&(ks))
# define des_ecb_encrypt(i,o,k,e) \
DES_ecb_encrypt((i),(o),&(k),(e))
#endif /* OpenSSL 0.9.7+ w/o old DES support */
#include <sasl.h>
#define MD5_H /* suppress internal MD5 */
#include <saslplug.h>
#include "plugin_common.h"
/***************************** Common Section *****************************/
static const char plugin_id[] = "$Id: ntlm.c,v 1.33 2008/10/30 14:19:46 mel Exp $";
#ifdef WIN32
static ssize_t writev (SOCKET fd, const struct iovec *iov, size_t iovcnt);
ssize_t writev (SOCKET fd, const struct iovec *iov, size_t iovcnt)
{
ssize_t nwritten; /* amount written */
ssize_t nbytes;
size_t i;
nbytes = 0;
for (i = 0; i < iovcnt; i++) {
if ((nwritten = send (fd, iov[i].iov_base, iov[i].iov_len, 0)) == SOCKET_ERROR) {
/* Unless socket is nonblocking, we should always write everything */
return (-1);
}
nbytes += nwritten;
if (nwritten < iov[i].iov_len) {
break;
}
}
return (nbytes);
}
#endif /* WIN32 */
#ifndef UINT16_MAX
#define UINT16_MAX 65535U
#endif
#if UINT_MAX == UINT16_MAX
typedef unsigned int uint16;
#elif USHRT_MAX == UINT16_MAX
typedef unsigned short uint16;
#else
#error dont know what to use for uint16
#endif
#ifndef UINT32_MAX
#define UINT32_MAX 4294967295U
#endif
#if UINT_MAX == UINT32_MAX
typedef unsigned int uint32;
#elif ULONG_MAX == UINT32_MAX
typedef unsigned long uint32;
#elif USHRT_MAX == UINT32_MAX
typedef unsigned short uint32;
#else
#error dont know what to use for uint32
#endif
#define NTLM_SIGNATURE "NTLMSSP"
enum {
NTLM_TYPE_REQUEST = 1,
NTLM_TYPE_CHALLENGE = 2,
NTLM_TYPE_RESPONSE = 3
};
enum {
NTLM_USE_UNICODE = 0x00001,
NTLM_USE_ASCII = 0x00002,
NTLM_ASK_TARGET = 0x00004,
NTLM_AUTH_NTLM = 0x00200,
NTLM_ALWAYS_SIGN = 0x08000,
NTLM_TARGET_IS_DOMAIN = 0x10000,
NTLM_TARGET_IS_SERVER = 0x20000,
NTLM_FLAGS_MASK = 0x0ffff
};
enum {
NTLM_NONCE_LENGTH = 8,
NTLM_HASH_LENGTH = 21,
NTLM_RESP_LENGTH = 24,
NTLM_SESSKEY_LENGTH = 16,
};
enum {
NTLM_SIG_OFFSET = 0,
NTLM_TYPE_OFFSET = 8,
NTLM_TYPE1_FLAGS_OFFSET = 12,
NTLM_TYPE1_DOMAIN_OFFSET = 16,
NTLM_TYPE1_WORKSTN_OFFSET = 24,
NTLM_TYPE1_DATA_OFFSET = 32,
NTLM_TYPE1_MINSIZE = 16,
NTLM_TYPE2_TARGET_OFFSET = 12,
NTLM_TYPE2_FLAGS_OFFSET = 20,
NTLM_TYPE2_CHALLENGE_OFFSET = 24,
NTLM_TYPE2_CONTEXT_OFFSET = 32,
NTLM_TYPE2_TARGETINFO_OFFSET= 40,
NTLM_TYPE2_DATA_OFFSET = 48,
NTLM_TYPE2_MINSIZE = 32,
NTLM_TYPE3_LMRESP_OFFSET = 12,
NTLM_TYPE3_NTRESP_OFFSET = 20,
NTLM_TYPE3_DOMAIN_OFFSET = 28,
NTLM_TYPE3_USER_OFFSET = 36,
NTLM_TYPE3_WORKSTN_OFFSET = 44,
NTLM_TYPE3_SESSIONKEY_OFFSET= 52,
NTLM_TYPE3_FLAGS_OFFSET = 60,
NTLM_TYPE3_DATA_OFFSET = 64,
NTLM_TYPE3_MINSIZE = 52,
NTLM_BUFFER_LEN_OFFSET = 0,
NTLM_BUFFER_MAXLEN_OFFSET = 2,
NTLM_BUFFER_OFFSET_OFFSET = 4,
NTLM_BUFFER_SIZE = 8
};
/* return the length of a string (even if it is NULL) */
#define xstrlen(s) (s ? strlen(s) : 0)
/* machine-independent routines to convert to/from Intel byte-order */
#define htois(is, hs) \
(is)[0] = hs & 0xff; \
(is)[1] = hs >> 8
#define itohs(is) \
((is)[0] | ((is)[1] << 8))
#define htoil(il, hl) \
(il)[0] = hl & 0xff; \
(il)[1] = (hl >> 8) & 0xff; \
(il)[2] = (hl >> 16) & 0xff; \
(il)[3] = hl >> 24
#define itohl(il) \
((il)[0] | ((il)[1] << 8) | ((il)[2] << 16) | ((il)[3] << 24))
/* convert string to all upper case */
static const char *ucase(const char *str, size_t len)
{
char *cp = (char *) str;
if (!len) len = xstrlen(str);
while (len && cp && *cp) {
*cp = toupper((int) *cp);
cp++;
len--;
}
return (str);
}
/* copy src to dst as unicode (in Intel byte-order) */
static void to_unicode(u_char *dst, const char *src, int len)
{
for (; len; len--) {
*dst++ = *src++;
*dst++ = 0;
}
}
/* copy unicode src (in Intel byte-order) to dst */
static void from_unicode(char *dst, u_char *src, int len)
{
for (; len; len--) {
*dst++ = *src & 0x7f;
src += 2;
}
}
/* load a string into an NTLM buffer */
static void load_buffer(u_char *buf, const u_char *str, uint16 len,
int unicode, u_char *base, uint32 *offset)
{
if (len) {
if (unicode) {
to_unicode(base + *offset, str, len);
len *= 2;
}
else {
memcpy(base + *offset, str, len);
}
}
htois(buf + NTLM_BUFFER_LEN_OFFSET, len);
htois(buf + NTLM_BUFFER_MAXLEN_OFFSET, len);
htoil(buf + NTLM_BUFFER_OFFSET_OFFSET, *offset);
*offset += len;
}
/* unload a string from an NTLM buffer */
static int unload_buffer(const sasl_utils_t *utils, const u_char *buf,
u_char **str, unsigned *outlen,
int unicode, const u_char *base, unsigned msglen)
{
uint16 len = itohs(buf + NTLM_BUFFER_LEN_OFFSET);
if (len) {
uint32 offset;
*str = utils->malloc(len + 1); /* add 1 for NUL */
if (*str == NULL) {
MEMERROR(utils);
return SASL_NOMEM;
}
offset = itohl(buf + NTLM_BUFFER_OFFSET_OFFSET);
/* sanity check */
if (offset > msglen || len > (msglen - offset)) return SASL_BADPROT;
if (unicode) {
len /= 2;
from_unicode((char *) *str, (u_char *) base + offset, len);
}
else
memcpy(*str, base + offset, len);
(*str)[len] = '\0'; /* add NUL */
}
else {
*str = NULL;
}
if (outlen) *outlen = len;
return SASL_OK;
}
/*
* NTLM encryption/authentication routines per section 2.10 of
* draft-leach-cifs-v1-spec-02
*/
static void E(unsigned char *out, unsigned char *K, unsigned Klen,
unsigned char *D, unsigned Dlen)
{
unsigned k, d;
des_cblock K64;
des_key_schedule ks;
unsigned char *Dp;
#define KEY_SIZE 7
#define BLOCK_SIZE 8
for (k = 0; k < Klen; k += KEY_SIZE, K += KEY_SIZE) {
/* convert 56-bit key to 64-bit */
K64[0] = K[0];
K64[1] = ((K[0] << 7) & 0xFF) | (K[1] >> 1);
K64[2] = ((K[1] << 6) & 0xFF) | (K[2] >> 2);
K64[3] = ((K[2] << 5) & 0xFF) | (K[3] >> 3);
K64[4] = ((K[3] << 4) & 0xFF) | (K[4] >> 4);
K64[5] = ((K[4] << 3) & 0xFF) | (K[5] >> 5);
K64[6] = ((K[5] << 2) & 0xFF) | (K[6] >> 6);
K64[7] = (K[6] << 1) & 0xFF;
des_set_odd_parity(&K64); /* XXX is this necessary? */
des_set_key(&K64, ks);
for (d = 0, Dp = D; d < Dlen;
d += BLOCK_SIZE, Dp += BLOCK_SIZE, out += BLOCK_SIZE) {
des_ecb_encrypt((void *) Dp, (void *) out, ks, DES_ENCRYPT);
}
}
}
static unsigned char *P16_lm(unsigned char *P16, sasl_secret_t *passwd,
const sasl_utils_t *utils __attribute__((unused)),
char **buf __attribute__((unused)),
unsigned *buflen __attribute__((unused)),
int *result)
{
char P14[14];
unsigned char S8[] = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
strncpy(P14, passwd->data, sizeof(P14));
ucase(P14, sizeof(P14));
E(P16, P14, sizeof(P14), S8, sizeof(S8));
*result = SASL_OK;
return P16;
}
static unsigned char *P16_nt(unsigned char *P16, sasl_secret_t *passwd,
const sasl_utils_t *utils,
char **buf, unsigned *buflen, int *result)
{
if (_plug_buf_alloc(utils, buf, buflen, 2 * passwd->len) != SASL_OK) {
SETERROR(utils, "cannot allocate P16_nt unicode buffer");
*result = SASL_NOMEM;
}
else {
to_unicode(*buf, passwd->data, passwd->len);
MD4(*buf, 2 * passwd->len, P16);
*result = SASL_OK;
}
return P16;
}
static unsigned char *P21(unsigned char *P21, sasl_secret_t *passwd,
unsigned char * (*P16)(unsigned char *,
sasl_secret_t *,
const sasl_utils_t *,
char **, unsigned *, int *),
const sasl_utils_t *utils,
char **buf, unsigned *buflen, int *result)
{
memset(P16(P21, passwd, utils, buf, buflen, result) + 16, 0, 5);
return P21;
}
static unsigned char *P24(unsigned char *P24, unsigned char *P21,
unsigned char *C8)
{
E(P24, P21, NTLM_HASH_LENGTH, C8, NTLM_NONCE_LENGTH);
return P24;
}
static unsigned char *V2(unsigned char *V2, sasl_secret_t *passwd,
const char *authid, const char *target,
const unsigned char *challenge,
const unsigned char *blob, unsigned bloblen,
const sasl_utils_t *utils,
char **buf, unsigned *buflen, int *result)
{
HMAC_CTX ctx;
unsigned char hash[EVP_MAX_MD_SIZE];
char *upper;
unsigned int len;
/* Allocate enough space for the unicode target */
len = (unsigned int) (strlen(authid) + xstrlen(target));
if (_plug_buf_alloc(utils, buf, buflen, 2 * len + 1) != SASL_OK) {
SETERROR(utils, "cannot allocate NTLMv2 hash");
*result = SASL_NOMEM;
}
else {
/* NTLMv2hash = HMAC-MD5(NTLMhash, unicode(ucase(authid + domain))) */
P16_nt(hash, passwd, utils, buf, buflen, result);
/* Use the tail end of the buffer for ucase() conversion */
upper = *buf + len;
strcpy(upper, authid);
if (target) strcat(upper, target);
ucase(upper, len);
to_unicode(*buf, upper, len);
HMAC(EVP_md5(), hash, MD4_DIGEST_LENGTH, *buf, 2 * len, hash, &len);
/* V2 = HMAC-MD5(NTLMv2hash, challenge + blob) + blob */
HMAC_Init(&ctx, hash, len, EVP_md5());
HMAC_Update(&ctx, challenge, NTLM_NONCE_LENGTH);
HMAC_Update(&ctx, blob, bloblen);
HMAC_Final(&ctx, V2, &len);
HMAC_cleanup(&ctx);
/* the blob is concatenated outside of this function */
*result = SASL_OK;
}
return V2;
}
/***************************** Server Section *****************************/
typedef struct server_context {
int state;
uint32 flags;
unsigned char nonce[NTLM_NONCE_LENGTH];
/* per-step mem management */
char *out_buf;
unsigned out_buf_len;
/* socket to remote authentication host */
SOCKET sock;
} server_context_t;
#define N(a) (sizeof (a) / sizeof (a[0]))
#define SMB_HDR_PROTOCOL "\xffSMB"
typedef struct {
unsigned char protocol[4];
unsigned char command;
uint32 status;
unsigned char flags;
uint16 flags2;
uint16 PidHigh;
unsigned char extra[10];
uint16 tid;
uint16 pid;
uint16 uid;
uint16 mid;
} SMB_Header;
typedef struct {
uint16 dialect_index;
unsigned char security_mode;
uint16 max_mpx_count;
uint16 max_number_vcs;
uint32 max_buffer_size;
uint32 max_raw_size;
uint32 session_key;
uint32 capabilities;
uint32 system_time_low;
uint32 system_time_high;
uint16 server_time_zone;
unsigned char encryption_key_length;
} SMB_NegProt_Resp;
typedef struct {
unsigned char andx_command;
unsigned char andx_reserved;
uint16 andx_offset;
uint16 max_buffer_size;
uint16 max_mpx_count;
uint16 vc_number;
uint32 session_key;
uint16 case_insensitive_passwd_len;
uint16 case_sensitive_passwd_len;
uint32 reserved;
uint32 capabilities;
} SMB_SessionSetup;
typedef struct {
unsigned char andx_command;
unsigned char andx_reserved;
uint16 andx_offset;
uint16 action;
} SMB_SessionSetup_Resp;
enum {
NBT_SESSION_REQUEST = 0x81,
NBT_POSITIVE_SESSION_RESP = 0x82,
NBT_NEGATIVE_SESSION_RESP = 0x83,
NBT_ERR_NO_LISTEN_CALLED = 0x80,
NBT_ERR_NO_LISTEN_CALLING = 0x81,
NBT_ERR_CALLED_NOT_PRESENT = 0x82,
NBT_ERR_INSUFFICIENT_RESRC = 0x83,
NBT_ERR_UNSPECIFIED = 0x8F,
SMB_HDR_SIZE = 32,
SMB_COM_NEGOTIATE_PROTOCOL = 0x72,
SMB_COM_SESSION_SETUP_ANDX = 0x73,
SMB_COM_NONE = 0xFF,
SMB_FLAGS_SERVER_TO_REDIR = 0x80,
SMB_FLAGS2_ERR_STATUS = 0x4000,
SMB_FLAGS2_UNICODE = 0x8000,
SMB_NEGPROT_RESP_SIZE = 34,
SMB_SECURITY_MODE_USER = 0x1,
SMB_SECURITY_MODE_ENCRYPT = 0x2,
SMB_SECURITY_MODE_SIGN = 0x4,
SMB_SECURITY_MODE_SIGN_REQ = 0x8,
SMB_CAP_UNICODE = 0x0004,
SMB_CAP_STATUS32 = 0x0040,
SMB_CAP_EXTENDED_SECURITY = 0x80000000,
SMB_SESSION_SETUP_SIZE = 26,
SMB_SESSION_SETUP_RESP_SIZE = 6,
SMB_REQUEST_MODE_GUEST = 0x1
};
static const char *SMB_DIALECTS[] = {
#if 0
"\x02PC NETWORK PROGRAM 1.0",
"\x02PCLAN1.0",
"\x02MICROSOFT NETWORKS 1.03",
"\x02MICROSOFT NETWORKS 3.0",
"\x02LANMAN1.0",
"\x02Windows for Workgroups 3.1a",
"\x02LM1.2X002",
"\x02DOS LM1.2X002",
"\x02DOS LANLAM2.1",
"\x02LANMAN2.1",
#endif
"\x02NT LM 0.12"
};
static void load_smb_header(unsigned char buf[], SMB_Header *hdr)
{
unsigned char *p = buf;
memcpy(p, SMB_HDR_PROTOCOL, 4); p += 4;
*p++ = hdr->command;
htoil(p, hdr->status); p += 4;
*p++ = hdr->flags;
htois(p, hdr->flags2); p += 2;
htois(p, hdr->PidHigh); p += 2;
memcpy(p, hdr->extra, 10); p += 10;
htois(p, hdr->tid); p += 2;
htois(p, hdr->pid); p += 2;
htois(p, hdr->uid); p += 2;
htois(p, hdr->mid);
}
static void unload_smb_header(unsigned char buf[], SMB_Header *hdr)
{
unsigned char *p = buf;
memcpy(hdr->protocol, p, 4); p += 4;
hdr->command = *p++;
hdr->status = itohl(p); p += 4;
hdr->flags = *p++;
hdr->flags2 = itohs(p); p += 2;
hdr->PidHigh = itohs(p); p += 2;
memcpy(hdr->extra, p, 10); p += 10;
hdr->tid = itohs(p); p += 2;
hdr->pid = itohs(p); p += 2;
hdr->uid = itohs(p); p += 2;
hdr->mid = itohs(p);
}
static void unload_negprot_resp(unsigned char buf[], SMB_NegProt_Resp *resp)
{
unsigned char *p = buf;
resp->dialect_index = itohs(p); p += 2;
resp->security_mode = *p++;
resp->max_mpx_count = itohs(p); p += 2;
resp->max_number_vcs = itohs(p); p += 2;
resp->max_buffer_size = itohl(p); p += 4;
resp->max_raw_size = itohl(p); p += 4;
resp->session_key = itohl(p); p += 4;
resp->capabilities = itohl(p); p += 4;
resp->system_time_low = itohl(p); p += 4;
resp->system_time_high = itohl(p); p += 4;
resp->server_time_zone = itohs(p); p += 2;
resp->encryption_key_length = *p;
}
static void load_session_setup(unsigned char buf[], SMB_SessionSetup *setup)
{
unsigned char *p = buf;
*p++ = setup->andx_command;
*p++ = setup->andx_reserved;
htois(p, setup->andx_offset); p += 2;
htois(p, setup->max_buffer_size); p += 2;
htois(p, setup->max_mpx_count); p += 2;
htois(p, setup->vc_number); p += 2;
htoil(p, setup->session_key); p += 4;
htois(p, setup->case_insensitive_passwd_len); p += 2;
htois(p, setup->case_sensitive_passwd_len); p += 2;
htoil(p, setup->reserved); p += 4;
htoil(p, setup->capabilities); p += 4;
}
static void unload_session_setup_resp(unsigned char buf[],
SMB_SessionSetup_Resp *resp)
{
unsigned char *p = buf;
resp->andx_command = *p++;
resp->andx_reserved = *p++;
resp->andx_offset = itohs(p); p += 2;
resp->action = itohs(p);
}
/*
* Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
* until all the data is written out or an error occurs.
*/
static int retry_writev(SOCKET fd, struct iovec *iov, int iovcnt)
{
int n;
int i;
int written = 0;
static int iov_max =
#ifdef MAXIOV
MAXIOV
#else
#ifdef IOV_MAX
IOV_MAX
#else
8192
#endif
#endif
;
for (;;) {
while (iovcnt && iov[0].iov_len == 0) {
iov++;
iovcnt--;
}
if (!iovcnt) return written;
n = (int) writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
if (n == -1) {
#ifndef WIN32
if (errno == EINVAL && iov_max > 10) {
iov_max /= 2;
continue;
}
if (errno == EINTR) continue;
#endif
return -1;
}
written += n;
for (i = 0; i < iovcnt; i++) {
if ((int) iov[i].iov_len > n) {
iov[i].iov_base = (char *) iov[i].iov_base + n;
iov[i].iov_len -= n;
break;
}
n -= iov[i].iov_len;
iov[i].iov_len = 0;
}
if (i == iovcnt) return written;
}
}
/*
* Keep calling the read() system call with 'fd', 'buf', and 'nbyte'
* until all the data is read in or an error occurs.
*/
static int retry_read(SOCKET fd, char *buf0, unsigned nbyte)
{
int n;
int nread = 0;
char *buf = buf0;
if (nbyte == 0) return 0;
for (;;) {
/* Can't use read() on sockets on Windows, but recv works on all platforms */
n = recv (fd, buf, nbyte, 0);
if (n == -1 || n == 0) {
#ifndef WIN32
if (errno == EINTR || errno == EAGAIN) continue;
#endif
return -1;
}
nread += n;
if (n >= (int) nbyte) return nread;
buf += n;
nbyte -= n;
}
}
static void make_netbios_name(const char *in, unsigned char out[])
{
size_t i, j = 0, n;
/* create a NetBIOS name from the DNS name
*
* - use up to the first 16 chars of the first part of the hostname
* - convert to all uppercase
* - use the tail end of the output buffer as temp space
*/
n = strcspn(in, ".");
if (n > 16) n = 16;
strncpy(out+18, in, n);
in = out+18;
ucase(in, n);
out[j++] = 0x20;
for (i = 0; i < n; i++) {
out[j++] = ((in[i] >> 4) & 0xf) + 0x41;
out[j++] = (in[i] & 0xf) + 0x41;
}
for (; i < 16; i++) {
out[j++] = ((0x20 >> 4) & 0xf) + 0x41;
out[j++] = (0x20 & 0xf) + 0x41;
}
out[j] = 0;
}
static SOCKET smb_connect_server(const sasl_utils_t *utils, const char *client,
const char *server)
{
struct addrinfo hints;
struct addrinfo *ai = NULL, *r;
SOCKET s = (SOCKET) -1;
int err;
char * error_str;
#ifdef WIN32
DWORD saved_errno;
#else
int saved_errno;
#endif
int niflags;
char *port = "139";
char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
unsigned char called[34];
unsigned char calling[34];
struct iovec iov[3];
uint32 pkt;
int rc;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
if ((err = getaddrinfo(server, port, &hints, &ai)) != 0) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: getaddrinfo %s/%s: %s",
server, port, gai_strerror(err));
return -1;
}
/* Make sure we have AF_INET or AF_INET6 addresses. */
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
utils->log(NULL, SASL_LOG_ERR, "NTLM: no IP address info for %s",
ai->ai_canonname ? ai->ai_canonname : server);
freeaddrinfo(ai);
return -1;
}
/* establish connection to authentication server */
for (r = ai; r; r = r->ai_next) {
s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
if (s < 0)
continue;
if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
break;
#ifdef WIN32
saved_errno = WSAGetLastError();
#else
saved_errno = errno;
#endif
closesocket (s);
s = -1;
niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
#ifdef NI_WITHSCOPEID
if (r->ai_family == AF_INET6)
niflags |= NI_WITHSCOPEID;
#endif
if (getnameinfo(r->ai_addr, r->ai_addrlen, hbuf, sizeof(hbuf),
pbuf, sizeof(pbuf), niflags) != 0) {
strcpy(hbuf, "unknown");
strcpy(pbuf, "unknown");
}
/* Can't use errno (and %m), as it doesn't contain
* the socket error on Windows */
error_str = _plug_get_error_message (utils, saved_errno);
utils->log(NULL, SASL_LOG_WARN, "NTLM: connect %s[%s]/%s: %s",
ai->ai_canonname ? ai->ai_canonname : server,
hbuf,
pbuf,
error_str);
utils->free (error_str);
}
if (s < 0) {
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, NULL, 0,
pbuf, sizeof(pbuf), NI_NUMERICSERV) != 0) {
strcpy(pbuf, "unknown");
}
utils->log(NULL, SASL_LOG_ERR, "NTLM: couldn't connect to %s/%s",
ai->ai_canonname ? ai->ai_canonname : server, pbuf);
freeaddrinfo(ai);
return -1;
}
freeaddrinfo(ai);
/*** send NetBIOS session request ***/
/* get length of data */
pkt = sizeof(called) + sizeof(calling);
/* make sure length is less than 17 bits */
if (pkt >= (1 << 17)) {
closesocket(s);
return -1;
}
/* prepend the packet type */
pkt |= (NBT_SESSION_REQUEST << 24);
pkt = htonl(pkt);
/* XXX should determine the real NetBIOS name */
make_netbios_name(server, called);
make_netbios_name(client, calling);
iov[0].iov_base = (void *) &pkt;
iov[0].iov_len = sizeof(pkt);
iov[1].iov_base = called;
iov[1].iov_len = sizeof(called);
iov[2].iov_base = calling;
iov[2].iov_len = sizeof(calling);
rc = retry_writev(s, iov, N(iov));
if (rc == -1) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error sending NetBIOS session request");
closesocket(s);
return -1;
}
rc = retry_read(s, (char *) &pkt, sizeof(pkt));
pkt = ntohl(pkt);
if (rc == -1 || pkt != (uint32) (NBT_POSITIVE_SESSION_RESP << 24)) {
unsigned char ec = NBT_ERR_UNSPECIFIED;
char *errstr;
retry_read(s, (char *) &ec, sizeof(ec));
switch (ec) {
case NBT_ERR_NO_LISTEN_CALLED:
errstr = "Not listening on called name";
break;
case NBT_ERR_NO_LISTEN_CALLING:
errstr = "Not listening for calling name";
break;
case NBT_ERR_CALLED_NOT_PRESENT:
errstr = "Called name not present";
break;
case NBT_ERR_INSUFFICIENT_RESRC:
errstr = "Called name present, but insufficient resources";
break;
default:
errstr = "Unspecified error";
}
utils->log(NULL, SASL_LOG_ERR,
"NTLM: negative NetBIOS session response: %s", errstr);
closesocket(s);
return -1;
}
return s;
}
static int smb_negotiate_protocol(const sasl_utils_t *utils,
server_context_t *text, char **domain)
{
SMB_Header hdr;
SMB_NegProt_Resp resp;
unsigned char hbuf[SMB_HDR_SIZE], *p;
unsigned char wordcount = 0;
unsigned char bc[sizeof(uint16)];
uint16 bytecount;
uint32 len, nl;
int n_dialects = N(SMB_DIALECTS);
struct iovec iov[4+N(SMB_DIALECTS)];
int i, n;
int rc;
pid_t current_pid;
/*** create a negotiate protocol request ***/
/* create a header */
memset(&hdr, 0, sizeof(hdr));
hdr.command = SMB_COM_NEGOTIATE_PROTOCOL;
#if 0
hdr.flags2 = SMB_FLAGS2_ERR_STATUS;
if (text->flags & NTLM_USE_UNICODE) hdr.flags2 |= SMB_FLAGS2_UNICODE;
#endif
current_pid = getpid();
if (sizeof(current_pid) <= 2) {
hdr.pid = (uint16) current_pid;
hdr.PidHigh = 0;
} else {
hdr.pid = (uint16) (((uint32) current_pid) & 0xFFFF);
hdr.PidHigh = (uint16) (((uint32) current_pid) >> 16);
}
load_smb_header(hbuf, &hdr);
/* put together all of the pieces of the request */
n = 0;
iov[n].iov_base = (void *) &nl;
iov[n++].iov_len = sizeof(len);
iov[n].iov_base = hbuf;
iov[n++].iov_len = SMB_HDR_SIZE;
iov[n].iov_base = &wordcount;
iov[n++].iov_len = sizeof(wordcount);
iov[n].iov_base = (void *) &bc;
iov[n++].iov_len = sizeof(bc);
/* add our supported dialects */
for (i = 0; i < n_dialects; i++) {
iov[n].iov_base = (char *) SMB_DIALECTS[i];
iov[n++].iov_len = (long) strlen(SMB_DIALECTS[i]) + 1;
}
/* total up the lengths */
len = bytecount = 0;
for (i = 1; i < 4; i++) len += iov[i].iov_len;
for (i = 4; i < n; i++) bytecount += (uint16) iov[i].iov_len;
len += bytecount;
nl = htonl(len);
htois((char *) &bc, bytecount);
/* send it */
rc = retry_writev(text->sock, iov, n);
if (rc == -1) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error sending NEGPROT request");
return SASL_FAIL;
}
/*** read the negotiate protocol response ***/
/* read the total length */
rc = retry_read(text->sock, (char *) &nl, sizeof(nl));
if (rc < (int) sizeof(nl)) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error reading NEGPROT response length");
return SASL_FAIL;
}
/* read the data */
len = ntohl(nl);
if (_plug_buf_alloc(utils, &text->out_buf, &text->out_buf_len,
len) != SASL_OK) {
SETERROR(utils, "cannot allocate NTLM NEGPROT response buffer");
return SASL_NOMEM;
}
rc = retry_read(text->sock, text->out_buf, len);
if (rc < (int) len) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error reading NEGPROT response");
return SASL_FAIL;
}
p = text->out_buf;
/* parse the header */
if (len < SMB_HDR_SIZE) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: not enough data for NEGPROT response header");
return SASL_FAIL;
}
unload_smb_header(p, &hdr);
p += SMB_HDR_SIZE;
len -= SMB_HDR_SIZE;
/* sanity check the header */
if (memcmp(hdr.protocol, SMB_HDR_PROTOCOL, 4) /* correct protocol */
|| hdr.command != SMB_COM_NEGOTIATE_PROTOCOL /* correct command */
|| hdr.status /* no errors */
|| !(hdr.flags & SMB_FLAGS_SERVER_TO_REDIR)) { /* response */
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error in NEGPROT response header: %ld",
hdr.status);
return SASL_FAIL;
}
/* get the wordcount */
if (len < 1) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: not enough data for NEGPROT response wordcount");
return SASL_FAIL;
}
wordcount = *p++;
len--;
/* parse the parameters */
if (wordcount != SMB_NEGPROT_RESP_SIZE / sizeof(uint16)) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: incorrect NEGPROT wordcount for NT LM 0.12");
return SASL_FAIL;
}
unload_negprot_resp(p, &resp);
p += SMB_NEGPROT_RESP_SIZE;
len -= SMB_NEGPROT_RESP_SIZE;
/* sanity check the parameters */
if (resp.dialect_index != 0
|| !(resp.security_mode & SMB_SECURITY_MODE_USER)
|| !(resp.security_mode & SMB_SECURITY_MODE_ENCRYPT)
|| resp.security_mode & SMB_SECURITY_MODE_SIGN_REQ
|| resp.capabilities & SMB_CAP_EXTENDED_SECURITY
|| resp.encryption_key_length != NTLM_NONCE_LENGTH) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error in NEGPROT response parameters");
return SASL_FAIL;
}
/* get the bytecount */
if (len < 2) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: not enough data for NEGPROT response bytecount");
return SASL_FAIL;
}
bytecount = itohs(p);
p += 2;
len -= 2;
if (len != bytecount) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: incorrect bytecount for NEGPROT response data");
return SASL_FAIL;
}
/* parse the data */
memcpy(text->nonce, p, resp.encryption_key_length);
p += resp.encryption_key_length;
len -= resp.encryption_key_length;
/* if client asked for target, send domain */
if (text->flags & NTLM_ASK_TARGET) {
*domain = utils->malloc(len);
if (domain == NULL) {
MEMERROR(utils);
return SASL_NOMEM;
}
memcpy(*domain, p, len);
from_unicode(*domain, *domain, len);
text->flags |= NTLM_TARGET_IS_DOMAIN;
}
return SASL_OK;
}
static int smb_session_setup(const sasl_utils_t *utils, server_context_t *text,
const char *authid, char *domain,
unsigned char *lm_resp, unsigned lm_resp_len,
unsigned char *nt_resp, unsigned nt_resp_len)
{
SMB_Header hdr;
SMB_SessionSetup setup;
SMB_SessionSetup_Resp resp;
unsigned char hbuf[SMB_HDR_SIZE], sbuf[SMB_SESSION_SETUP_SIZE], *p;
unsigned char wordcount = SMB_SESSION_SETUP_SIZE / sizeof(uint16);
unsigned char bc[sizeof(uint16)];
uint16 bytecount;
uint32 len, nl;
struct iovec iov[12];
int i, n;
int rc;
#ifdef WIN32
char osbuf[80];
#else
char osbuf[2*SYS_NMLN+2];
#endif
char lanman[20];
pid_t current_pid;
/*** create a session setup request ***/
/* create a header */
memset(&hdr, 0, sizeof(hdr));
hdr.command = SMB_COM_SESSION_SETUP_ANDX;
#if 0
hdr.flags2 = SMB_FLAGS2_ERR_STATUS;
if (text->flags & NTLM_USE_UNICODE) hdr.flags2 |= SMB_FLAGS2_UNICODE;
#endif
current_pid = getpid();
if (sizeof(current_pid) <= 2) {
hdr.pid = (uint16) current_pid;
hdr.PidHigh = 0;
} else {
hdr.pid = (uint16) (((uint32) current_pid) & 0xFFFF);
hdr.PidHigh = (uint16) (((uint32) current_pid) >> 16);
}
load_smb_header(hbuf, &hdr);
/* create a the setup parameters */
memset(&setup, 0, sizeof(setup));
setup.andx_command = SMB_COM_NONE;
setup.max_buffer_size = 0xFFFF;
if (lm_resp) setup.case_insensitive_passwd_len = lm_resp_len;
if (nt_resp) setup.case_sensitive_passwd_len = nt_resp_len;
#if 0
if (text->flags & NTLM_USE_UNICODE)
setup.capabilities = SMB_CAP_UNICODE;
#endif
load_session_setup(sbuf, &setup);
_plug_snprintf_os_info (osbuf, sizeof(osbuf));
snprintf(lanman, sizeof(lanman), "Cyrus SASL %u.%u.%u",
SASL_VERSION_MAJOR, SASL_VERSION_MINOR,
SASL_VERSION_STEP);
/* put together all of the pieces of the request */
n = 0;
iov[n].iov_base = (void *) &nl;
iov[n++].iov_len = sizeof(len);
iov[n].iov_base = hbuf;
iov[n++].iov_len = SMB_HDR_SIZE;
iov[n].iov_base = &wordcount;
iov[n++].iov_len = sizeof(wordcount);
iov[n].iov_base = sbuf;
iov[n++].iov_len = SMB_SESSION_SETUP_SIZE;
iov[n].iov_base = (void *) &bc;
iov[n++].iov_len = sizeof(bc);
if (lm_resp) {
iov[n].iov_base = lm_resp;
iov[n++].iov_len = NTLM_RESP_LENGTH;
}
if (nt_resp) {
iov[n].iov_base = nt_resp;
iov[n++].iov_len = NTLM_RESP_LENGTH;
}
iov[n].iov_base = (char*) authid;
iov[n++].iov_len = (long) strlen(authid) + 1;
if (!domain) domain = "";
iov[n].iov_base = domain;
iov[n++].iov_len = (long) strlen(domain) + 1;
iov[n].iov_base = osbuf;
iov[n++].iov_len = (long) strlen(osbuf) + 1;
iov[n].iov_base = lanman;
iov[n++].iov_len = (long) strlen(lanman) + 1;
/* total up the lengths */
len = bytecount = 0;
for (i = 1; i < 5; i++) len += iov[i].iov_len;
for (i = 5; i < n; i++) bytecount += (uint16) iov[i].iov_len;
len += bytecount;
nl = htonl(len);
htois((char *) &bc, bytecount);
/* send it */
rc = retry_writev(text->sock, iov, n);
if (rc == -1) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error sending SESSIONSETUP request");
return SASL_FAIL;
}
/*** read the session setup response ***/
/* read the total length */
rc = retry_read(text->sock, (char *) &nl, sizeof(nl));
if (rc < (int) sizeof(nl)) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error reading SESSIONSETUP response length");
return SASL_FAIL;
}
/* read the data */
len = ntohl(nl);
if (_plug_buf_alloc(utils, &text->out_buf, &text->out_buf_len,
len) != SASL_OK) {
SETERROR(utils,
"cannot allocate NTLM SESSIONSETUP response buffer");
return SASL_NOMEM;
}
rc = retry_read(text->sock, text->out_buf, len);
if (rc < (int) len) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error reading SESSIONSETUP response");
return SASL_FAIL;
}
p = text->out_buf;
/* parse the header */
if (len < SMB_HDR_SIZE) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: not enough data for SESSIONSETUP response header");
return SASL_FAIL;
}
unload_smb_header(p, &hdr);
p += SMB_HDR_SIZE;
len -= SMB_HDR_SIZE;
/* sanity check the header */
if (memcmp(hdr.protocol, SMB_HDR_PROTOCOL, 4) /* correct protocol */
|| hdr.command != SMB_COM_SESSION_SETUP_ANDX /* correct command */
|| !(hdr.flags & SMB_FLAGS_SERVER_TO_REDIR)) { /* response */
utils->log(NULL, SASL_LOG_ERR,
"NTLM: error in SESSIONSETUP response header");
return SASL_FAIL;
}
/* check auth success */
if (hdr.status) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: auth failure: %ld", hdr.status);
return SASL_BADAUTH;
}
/* get the wordcount */
if (len < 1) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: not enough data for SESSIONSETUP response wordcount");
return SASL_FAIL;
}
wordcount = *p++;
len--;
/* parse the parameters */
if (wordcount < SMB_SESSION_SETUP_RESP_SIZE / sizeof(uint16)) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: incorrect SESSIONSETUP wordcount");
return SASL_FAIL;
}
unload_session_setup_resp(p, &resp);
/* check auth success */
if (resp.action & SMB_REQUEST_MODE_GUEST) {
utils->log(NULL, SASL_LOG_ERR,
"NTLM: authenticated as guest");
return SASL_BADAUTH;
}
return SASL_OK;
}
/*
* Create a server challenge message (type 2) consisting of:
*
* signature (8 bytes)
* message type (uint32)
* target name (buffer)
* flags (uint32)
* challenge (8 bytes)
* context (8 bytes)
* target info (buffer)
* data
*/
static int create_challenge(const sasl_utils_t *utils,
char **buf, unsigned *buflen,
const char *target, uint32 flags,
const u_char *nonce, unsigned *outlen)
{
uint32 offset = NTLM_TYPE2_DATA_OFFSET;
u_char *base;
if (!nonce) {
SETERROR(utils, "need nonce for NTLM challenge");
return SASL_FAIL;
}
*outlen = offset + 2 * (unsigned) xstrlen(target);
if (_plug_buf_alloc(utils, buf, buflen, *outlen) != SASL_OK) {
SETERROR(utils, "cannot allocate NTLM challenge");
return SASL_NOMEM;
}
base = *buf;
memset(base, 0, *outlen);
memcpy(base + NTLM_SIG_OFFSET, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
htoil(base + NTLM_TYPE_OFFSET, NTLM_TYPE_CHALLENGE);
load_buffer(base + NTLM_TYPE2_TARGET_OFFSET,
ucase(target, 0), (uint16) xstrlen(target), flags & NTLM_USE_UNICODE,
base, &offset);
htoil(base + NTLM_TYPE2_FLAGS_OFFSET, flags);
memcpy(base + NTLM_TYPE2_CHALLENGE_OFFSET, nonce, NTLM_NONCE_LENGTH);
return SASL_OK;
}
static int ntlm_server_mech_new(void *glob_context __attribute__((unused)),
sasl_server_params_t *sparams,
const char *challenge __attribute__((unused)),
unsigned challen __attribute__((unused)),
void **conn_context)
{
server_context_t *text;
const char *serv;
unsigned int len;
SOCKET sock = (SOCKET) -1;
sparams->utils->getopt(sparams->utils->getopt_context,
"NTLM", "ntlm_server", &serv, &len);
if (serv) {
unsigned int i,j;
char *tmp, *next;
/* strip any whitespace */
if(_plug_strdup(sparams->utils, serv, &tmp, NULL) != SASL_OK) {
MEMERROR( sparams->utils );
return SASL_NOMEM;
}
for(i=0, j=0; i<len; i++) {
if(!isspace(tmp[i])) tmp[j++] = tmp[i];
}
tmp[j] = '\0';
next = tmp;
/* try to connect to a list of servers */
do {
serv = next;
next = strchr(serv, ',');
if(next) *(next++) = '\0';
/* try to start a NetBIOS session with the server */
sock = smb_connect_server(sparams->utils, sparams->serverFQDN, serv);
} while(sock == (SOCKET) -1 && next);
sparams->utils->free(tmp);
if (sock == (SOCKET) -1) return SASL_UNAVAIL;
}
/* holds state are in */
text = sparams->utils->malloc(sizeof(server_context_t));
if (text == NULL) {
MEMERROR( sparams->utils );
return SASL_NOMEM;
}
memset(text, 0, sizeof(server_context_t));
text->state = 1;
text->sock = sock;
*conn_context = text;
return SASL_OK;
}
static int ntlm_server_mech_step1(server_context_t *text,
sasl_server_params_t *sparams,
const char *clientin,
unsigned clientinlen,
const char **serverout,
unsigned *serveroutlen,
sasl_out_params_t *oparams __attribute__((unused)))
{
char *domain = NULL;
int result;
if (!clientin || clientinlen < NTLM_TYPE1_MINSIZE ||
memcmp(clientin, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) ||
itohl(clientin + NTLM_TYPE_OFFSET) != NTLM_TYPE_REQUEST) {
SETERROR(sparams->utils, "client didn't issue valid NTLM request");
return SASL_BADPROT;
}
text->flags = itohl(clientin + NTLM_TYPE1_FLAGS_OFFSET);
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"client flags: %x", text->flags);
text->flags &= NTLM_FLAGS_MASK; /* mask off the bits we don't support */
/* if client can do Unicode, turn off ASCII */
if (text->flags & NTLM_USE_UNICODE) text->flags &= ~NTLM_USE_ASCII;
if (text->sock == -1) {
/* generate challenge internally */
/* if client asked for target, use FQDN as server target */
if (text->flags & NTLM_ASK_TARGET) {
result = _plug_strdup(sparams->utils, sparams->serverFQDN,
&domain, NULL);
if (result != SASL_OK) return result;
text->flags |= NTLM_TARGET_IS_SERVER;
}
/* generate a nonce */
sparams->utils->rand(sparams->utils->rpool,
(char *) text->nonce, NTLM_NONCE_LENGTH);
}
else {
/* proxy the response/challenge */
result = smb_negotiate_protocol(sparams->utils, text, &domain);
if (result != SASL_OK) goto cleanup;
}
result = create_challenge(sparams->utils,
&text->out_buf, &text->out_buf_len,
domain, text->flags, text->nonce, serveroutlen);
if (result != SASL_OK) goto cleanup;
*serverout = text->out_buf;
text->state = 2;
result = SASL_CONTINUE;
cleanup:
if (domain) sparams->utils->free(domain);
return result;
}
static int ntlm_server_mech_step2(server_context_t *text,
sasl_server_params_t *sparams,
const char *clientin,
unsigned clientinlen,
const char **serverout __attribute__((unused)),
unsigned *serveroutlen __attribute__((unused)),
sasl_out_params_t *oparams)
{
unsigned char *lm_resp = NULL, *nt_resp = NULL;
char *domain = NULL, *authid = NULL;
unsigned lm_resp_len, nt_resp_len, domain_len, authid_len;
int result;
if (!clientin || clientinlen < NTLM_TYPE3_MINSIZE ||
memcmp(clientin, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) ||
itohl(clientin + NTLM_TYPE_OFFSET) != NTLM_TYPE_RESPONSE) {
SETERROR(sparams->utils, "client didn't issue valid NTLM response");
return SASL_BADPROT;
}
result = unload_buffer(sparams->utils, clientin + NTLM_TYPE3_LMRESP_OFFSET,
(u_char **) &lm_resp, &lm_resp_len, 0,
clientin, clientinlen);
if (result != SASL_OK) goto cleanup;
result = unload_buffer(sparams->utils, clientin + NTLM_TYPE3_NTRESP_OFFSET,
(u_char **) &nt_resp, &nt_resp_len, 0,
clientin, clientinlen);
if (result != SASL_OK) goto cleanup;
result = unload_buffer(sparams->utils, clientin + NTLM_TYPE3_DOMAIN_OFFSET,
(u_char **) &domain, &domain_len,
text->flags & NTLM_USE_UNICODE,
clientin, clientinlen);
if (result != SASL_OK) goto cleanup;
result = unload_buffer(sparams->utils, clientin + NTLM_TYPE3_USER_OFFSET,
(u_char **) &authid, &authid_len,
text->flags & NTLM_USE_UNICODE,
clientin, clientinlen);
if (result != SASL_OK) goto cleanup;
/* require at least one response and an authid */
if ((!lm_resp && !nt_resp) ||
(lm_resp && lm_resp_len < NTLM_RESP_LENGTH) ||
(nt_resp && nt_resp_len < NTLM_RESP_LENGTH) ||
!authid) {
SETERROR(sparams->utils, "client issued incorrect/nonexistent responses");
result = SASL_BADPROT;
goto cleanup;
}
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"client user: %s", authid);
if (domain) sparams->utils->log(NULL, SASL_LOG_DEBUG,
"client domain: %s", domain);
if (text->sock == -1) {
/* verify the response internally */
sasl_secret_t *password = NULL;
size_t pass_len;
const char *password_request[] = { SASL_AUX_PASSWORD,
NULL };
struct propval auxprop_values[2];
unsigned char hash[NTLM_HASH_LENGTH];
unsigned char resp[NTLM_RESP_LENGTH];
/* fetch user's password */
result = sparams->utils->prop_request(sparams->propctx, password_request);
if (result != SASL_OK) goto cleanup;
/* this will trigger the getting of the aux properties */
result = sparams->canon_user(sparams->utils->conn, authid, authid_len,
SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
if (result != SASL_OK) goto cleanup;
result = sparams->utils->prop_getnames(sparams->propctx,
password_request,
auxprop_values);
if (result < 0 ||
(!auxprop_values[0].name || !auxprop_values[0].values)) {
/* We didn't find this username */
SETERROR(sparams->utils, "no secret in database");
result = sparams->transition ? SASL_TRANS : SASL_NOUSER;
goto cleanup;
}
pass_len = strlen(auxprop_values[0].values[0]);
if (pass_len == 0) {
SETERROR(sparams->utils, "empty secret");
result = SASL_FAIL;
goto cleanup;
}
password = sparams->utils->malloc(sizeof(sasl_secret_t) + pass_len);
if (!password) {
result = SASL_NOMEM;
goto cleanup;
}
password->len = (unsigned) pass_len;
strncpy(password->data, auxprop_values[0].values[0], pass_len + 1);
/* erase the plaintext password */
sparams->utils->prop_erase(sparams->propctx, password_request[0]);
/* calculate our own response(s) and compare with client's */
result = SASL_OK;
if (nt_resp && (nt_resp_len > NTLM_RESP_LENGTH)) {
/* Try NTv2 response */
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"calculating NTv2 response");
V2(resp, password, authid, domain, text->nonce,
nt_resp + MD5_DIGEST_LENGTH, nt_resp_len - MD5_DIGEST_LENGTH,
sparams->utils, &text->out_buf, &text->out_buf_len,
&result);
/* No need to compare the blob */
if (memcmp(nt_resp, resp, MD5_DIGEST_LENGTH)) {
SETERROR(sparams->utils, "incorrect NTLMv2 response");
result = SASL_BADAUTH;
}
}
else if (nt_resp) {
/* Try NT response */
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"calculating NT response");
P24(resp, P21(hash, password, P16_nt, sparams->utils,
&text->out_buf, &text->out_buf_len, &result),
text->nonce);
if (memcmp(nt_resp, resp, NTLM_RESP_LENGTH)) {
SETERROR(sparams->utils, "incorrect NTLM response");
result = SASL_BADAUTH;
}
}
else if (lm_resp) {
/* Try LMv2 response */
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"calculating LMv2 response");
V2(resp, password, authid, domain, text->nonce,
lm_resp + MD5_DIGEST_LENGTH, lm_resp_len - MD5_DIGEST_LENGTH,
sparams->utils, &text->out_buf, &text->out_buf_len,
&result);
/* No need to compare the blob */
if (memcmp(lm_resp, resp, MD5_DIGEST_LENGTH)) {
/* Try LM response */
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"calculating LM response");
P24(resp, P21(hash, password, P16_lm, sparams->utils,
&text->out_buf, &text->out_buf_len, &result),
text->nonce);
if (memcmp(lm_resp, resp, NTLM_RESP_LENGTH)) {
SETERROR(sparams->utils, "incorrect LMv1/v2 response");
result = SASL_BADAUTH;
}
}
}
_plug_free_secret(sparams->utils, &password);
if (result != SASL_OK) goto cleanup;
}
else {
/* proxy the response */
result = smb_session_setup(sparams->utils, text, authid, domain,
lm_resp, lm_resp_len, nt_resp, nt_resp_len);
if (result != SASL_OK) goto cleanup;
result = sparams->canon_user(sparams->utils->conn, authid, authid_len,
SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
if (result != SASL_OK) goto cleanup;
}
/* set oparams */
oparams->doneflag = 1;
oparams->mech_ssf = 0;
oparams->maxoutbuf = 0;
oparams->encode_context = NULL;
oparams->encode = NULL;
oparams->decode_context = NULL;
oparams->decode = NULL;
oparams->param_version = 0;
result = SASL_OK;
cleanup:
if (lm_resp) sparams->utils->free(lm_resp);
if (nt_resp) sparams->utils->free(nt_resp);
if (domain) sparams->utils->free(domain);
if (authid) sparams->utils->free(authid);
return result;
}
static int ntlm_server_mech_step(void *conn_context,
sasl_server_params_t *sparams,
const char *clientin,
unsigned clientinlen,
const char **serverout,
unsigned *serveroutlen,
sasl_out_params_t *oparams)
{
server_context_t *text = (server_context_t *) conn_context;
*serverout = NULL;
*serveroutlen = 0;
sparams->utils->log(NULL, SASL_LOG_DEBUG,
"NTLM server step %d\n", text->state);
switch (text->state) {
case 1:
return ntlm_server_mech_step1(text, sparams, clientin, clientinlen,
serverout, serveroutlen, oparams);
case 2:
return ntlm_server_mech_step2(text, sparams, clientin, clientinlen,
serverout, serveroutlen, oparams);
default:
sparams->utils->log(NULL, SASL_LOG_ERR,
"Invalid NTLM server step %d\n", text->state);
return SASL_FAIL;
}
return SASL_FAIL; /* should never get here */
}
static void ntlm_server_mech_dispose(void *conn_context,
const sasl_utils_t *utils)
{
server_context_t *text = (server_context_t *) conn_context;
if (!text) return;
if (text->out_buf) utils->free(text->out_buf);
if (text->sock != -1) closesocket(text->sock);
utils->free(text);
}
static sasl_server_plug_t ntlm_server_plugins[] =
{
{
"NTLM", /* mech_name */
0, /* max_ssf */
SASL_SEC_NOPLAINTEXT
| SASL_SEC_NOANONYMOUS, /* security_flags */
SASL_FEAT_WANT_CLIENT_FIRST, /* features */
NULL, /* glob_context */
&ntlm_server_mech_new, /* mech_new */
&ntlm_server_mech_step, /* mech_step */
&ntlm_server_mech_dispose, /* mech_dispose */
NULL, /* mech_free */
NULL, /* setpass */
NULL, /* user_query */
NULL, /* idle */
NULL, /* mech_avail */
NULL /* spare */
}
};
int ntlm_server_plug_init(sasl_utils_t *utils,
int maxversion,
int *out_version,
sasl_server_plug_t **pluglist,
int *plugcount)
{
if (maxversion < SASL_SERVER_PLUG_VERSION) {
SETERROR(utils, "NTLM version mismatch");
return SASL_BADVERS;
}
*out_version = SASL_SERVER_PLUG_VERSION;
*pluglist = ntlm_server_plugins;
*plugcount = 1;
return SASL_OK;
}
/***************************** Client Section *****************************/
typedef struct client_context {
int state;
/* per-step mem management */
char *out_buf;
unsigned out_buf_len;
} client_context_t;
/*
* Create a client request (type 1) consisting of:
*
* signature (8 bytes)
* message type (uint32)
* flags (uint32)
* domain (buffer)
* workstation (buffer)
* data
*/
static int create_request(const sasl_utils_t *utils,
char **buf, unsigned *buflen,
const char *domain, const char *wkstn,
unsigned *outlen)
{
uint32 flags = ( NTLM_USE_UNICODE | NTLM_USE_ASCII |
NTLM_ASK_TARGET | NTLM_AUTH_NTLM );
uint32 offset = NTLM_TYPE1_DATA_OFFSET;
u_char *base;
*outlen = (unsigned) (offset + xstrlen(domain) + xstrlen(wkstn));
if (_plug_buf_alloc(utils, buf, buflen, *outlen) != SASL_OK) {
SETERROR(utils, "cannot allocate NTLM request");
return SASL_NOMEM;
}
base = *buf;
memset(base, 0, *outlen);
memcpy(base + NTLM_SIG_OFFSET, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
htoil(base + NTLM_TYPE_OFFSET, NTLM_TYPE_REQUEST);
htoil(base + NTLM_TYPE1_FLAGS_OFFSET, flags);
load_buffer(base + NTLM_TYPE1_DOMAIN_OFFSET,
domain, (uint16) xstrlen(domain), 0, base, &offset);
load_buffer(base + NTLM_TYPE1_WORKSTN_OFFSET,
wkstn, (uint16) xstrlen(wkstn), 0, base, &offset);
return SASL_OK;
}
/*
* Create a client response (type 3) consisting of:
*
* signature (8 bytes)
* message type (uint32)
* LM/LMv2 response (buffer)
* NTLM/NTLMv2 response (buffer)
* domain (buffer)
* user name (buffer)
* workstation (buffer)
* session key (buffer)
* flags (uint32)
* data
*/
static int create_response(const sasl_utils_t *utils,
char **buf, unsigned *buflen,
const u_char *lm_resp, const u_char *nt_resp,
const char *domain, const char *user,
const char *wkstn, const u_char *key,
uint32 flags, unsigned *outlen)
{
uint32 offset = NTLM_TYPE3_DATA_OFFSET;
u_char *base;
if (!lm_resp && !nt_resp) {
SETERROR(utils, "need at least one NT/LM response");
return SASL_FAIL;
}
*outlen = (unsigned) (offset + (flags & NTLM_USE_UNICODE ? 2 : 1) *
(xstrlen(domain) + xstrlen(user) + xstrlen(wkstn)));
if (lm_resp) *outlen += NTLM_RESP_LENGTH;
if (nt_resp) *outlen += NTLM_RESP_LENGTH;
if (key) *outlen += NTLM_SESSKEY_LENGTH;
if (_plug_buf_alloc(utils, buf, buflen, *outlen) != SASL_OK) {
SETERROR(utils, "cannot allocate NTLM response");
return SASL_NOMEM;
}
base = *buf;
memset(base, 0, *outlen);
memcpy(base + NTLM_SIG_OFFSET, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
htoil(base + NTLM_TYPE_OFFSET, NTLM_TYPE_RESPONSE);
load_buffer(base + NTLM_TYPE3_LMRESP_OFFSET,
lm_resp, lm_resp ? NTLM_RESP_LENGTH : 0, 0, base, &offset);
load_buffer(base + NTLM_TYPE3_NTRESP_OFFSET,
nt_resp, nt_resp ? NTLM_RESP_LENGTH : 0, 0, base, &offset);
load_buffer(base + NTLM_TYPE3_DOMAIN_OFFSET,
ucase(domain, 0), (uint16) xstrlen(domain), flags & NTLM_USE_UNICODE,
base, &offset);
load_buffer(base + NTLM_TYPE3_USER_OFFSET,
user, (uint16) xstrlen(user), flags & NTLM_USE_UNICODE, base, &offset);
load_buffer(base + NTLM_TYPE3_WORKSTN_OFFSET,
ucase(wkstn, 0), (uint16) xstrlen(wkstn), flags & NTLM_USE_UNICODE,
base, &offset);
load_buffer(base + NTLM_TYPE3_SESSIONKEY_OFFSET,
key, key ? NTLM_SESSKEY_LENGTH : 0, 0, base, &offset);
htoil(base + NTLM_TYPE3_FLAGS_OFFSET, flags);
return SASL_OK;
}
static int ntlm_client_mech_new(void *glob_context __attribute__((unused)),
sasl_client_params_t *params,
void **conn_context)
{
client_context_t *text;
/* holds state are in */
text = params->utils->malloc(sizeof(client_context_t));
if (text == NULL) {
MEMERROR( params->utils );
return SASL_NOMEM;
}
memset(text, 0, sizeof(client_context_t));
text->state = 1;
*conn_context = text;
return SASL_OK;
}
static int ntlm_client_mech_step1(client_context_t *text,
sasl_client_params_t *params,
const char *serverin __attribute__((unused)),
unsigned serverinlen __attribute__((unused)),
sasl_interact_t **prompt_need __attribute__((unused)),
const char **clientout,
unsigned *clientoutlen,
sasl_out_params_t *oparams __attribute__((unused)))
{
int result;
/* check if sec layer strong enough */
if (params->props.min_ssf > params->external_ssf) {
SETERROR(params->utils, "SSF requested of NTLM plugin");
return SASL_TOOWEAK;
}
/* we don't care about domain or wkstn */
result = create_request(params->utils, &text->out_buf, &text->out_buf_len,
NULL, NULL, clientoutlen);
if (result != SASL_OK) return result;
*clientout = text->out_buf;
text->state = 2;
return SASL_CONTINUE;
}
static int ntlm_client_mech_step2(client_context_t *text,
sasl_client_params_t *params,
const char *serverin,
unsigned serverinlen,
sasl_interact_t **prompt_need,
const char **clientout,
unsigned *clientoutlen,
sasl_out_params_t *oparams)
{
const char *authid = NULL;
sasl_secret_t *password = NULL;
unsigned int free_password; /* set if we need to free password */
char *domain = NULL;
int auth_result = SASL_OK;
int pass_result = SASL_OK;
uint32 flags = 0;
unsigned char hash[NTLM_HASH_LENGTH];
unsigned char resp[NTLM_RESP_LENGTH], *lm_resp = NULL, *nt_resp = NULL;
int result;
const char *sendv2;
if (!serverin || serverinlen < NTLM_TYPE2_MINSIZE ||
memcmp(serverin, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) ||
itohl(serverin + NTLM_TYPE_OFFSET) != NTLM_TYPE_CHALLENGE) {
SETERROR(params->utils, "server didn't issue valid NTLM challenge");
return SASL_BADPROT;
}
/* try to get the authid */
if (oparams->authid == NULL) {
auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
return auth_result;
}
/* try to get the password */
if (password == NULL) {
pass_result = _plug_get_password(params->utils, &password,
&free_password, prompt_need);
if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
return pass_result;
}
/* free prompts we got */
if (prompt_need && *prompt_need) {
params->utils->free(*prompt_need);
*prompt_need = NULL;
}
/* if there are prompts not filled in */
if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) {
/* make the prompt list */
result =
_plug_make_prompts(params->utils, prompt_need,
NULL, NULL,
auth_result == SASL_INTERACT ?
"Please enter your authentication name" : NULL,
NULL,
pass_result == SASL_INTERACT ?
"Please enter your password" : NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL);
if (result != SASL_OK) goto cleanup;
return SASL_INTERACT;
}
result = params->canon_user(params->utils->conn, authid, 0,
SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
if (result != SASL_OK) goto cleanup;
flags = itohl(serverin + NTLM_TYPE2_FLAGS_OFFSET);
params->utils->log(NULL, SASL_LOG_DEBUG,
"server flags: %x", flags);
flags &= NTLM_FLAGS_MASK; /* mask off the bits we don't support */
result = unload_buffer(params->utils, serverin + NTLM_TYPE2_TARGET_OFFSET,
(u_char **) &domain, NULL,
flags & NTLM_USE_UNICODE,
(u_char *) serverin, serverinlen);
if (result != SASL_OK) goto cleanup;
params->utils->log(NULL, SASL_LOG_DEBUG,
"server domain: %s", domain);
/* should we send a NTLMv2 response? */
params->utils->getopt(params->utils->getopt_context,
"NTLM", "ntlm_v2", &sendv2, NULL);
if (sendv2 &&
(*sendv2 == '1' || *sendv2 == 'y' ||
(*sendv2 == 'o' && *sendv2 == 'n') || *sendv2 == 't')) {
/* put the cnonce in place after the LMv2 HMAC */
char *cnonce = resp + MD5_DIGEST_LENGTH;
params->utils->log(NULL, SASL_LOG_DEBUG,
"calculating LMv2 response");
params->utils->rand(params->utils->rpool, cnonce, NTLM_NONCE_LENGTH);
V2(resp, password, oparams->authid, domain,
serverin + NTLM_TYPE2_CHALLENGE_OFFSET, cnonce, NTLM_NONCE_LENGTH,
params->utils, &text->out_buf, &text->out_buf_len, &result);
lm_resp = resp;
}
else if (flags & NTLM_AUTH_NTLM) {
params->utils->log(NULL, SASL_LOG_DEBUG,
"calculating NT response");
P24(resp, P21(hash, password, P16_nt, params->utils,
&text->out_buf, &text->out_buf_len, &result),
(unsigned char *) serverin + NTLM_TYPE2_CHALLENGE_OFFSET);
nt_resp = resp;
}
else {
params->utils->log(NULL, SASL_LOG_DEBUG,
"calculating LM response");
P24(resp, P21(hash, password, P16_lm, params->utils,
&text->out_buf, &text->out_buf_len, &result),
(unsigned char *) serverin + NTLM_TYPE2_CHALLENGE_OFFSET);
lm_resp = resp;
}
if (result != SASL_OK) goto cleanup;
/* we don't care about workstn or session key */
result = create_response(params->utils, &text->out_buf, &text->out_buf_len,
lm_resp, nt_resp, domain, oparams->authid,
NULL, NULL, flags, clientoutlen);
if (result != SASL_OK) goto cleanup;
*clientout = text->out_buf;
/* set oparams */
oparams->doneflag = 1;
oparams->mech_ssf = 0;
oparams->maxoutbuf = 0;
oparams->encode_context = NULL;
oparams->encode = NULL;
oparams->decode_context = NULL;
oparams->decode = NULL;
oparams->param_version = 0;
result = SASL_OK;
cleanup:
if (domain) params->utils->free(domain);
if (free_password) _plug_free_secret(params->utils, &password);
return result;
}
static int ntlm_client_mech_step(void *conn_context,
sasl_client_params_t *params,
const char *serverin,
unsigned serverinlen,
sasl_interact_t **prompt_need,
const char **clientout,
unsigned *clientoutlen,
sasl_out_params_t *oparams)
{
client_context_t *text = (client_context_t *) conn_context;
*clientout = NULL;
*clientoutlen = 0;
params->utils->log(NULL, SASL_LOG_DEBUG,
"NTLM client step %d\n", text->state);
switch (text->state) {
case 1:
return ntlm_client_mech_step1(text, params, serverin, serverinlen,
prompt_need, clientout, clientoutlen,
oparams);
case 2:
return ntlm_client_mech_step2(text, params, serverin, serverinlen,
prompt_need, clientout, clientoutlen,
oparams);
default:
params->utils->log(NULL, SASL_LOG_ERR,
"Invalid NTLM client step %d\n", text->state);
return SASL_FAIL;
}
return SASL_FAIL; /* should never get here */
}
static void ntlm_client_mech_dispose(void *conn_context,
const sasl_utils_t *utils)
{
client_context_t *text = (client_context_t *) conn_context;
if (!text) return;
if (text->out_buf) utils->free(text->out_buf);
utils->free(text);
}
static sasl_client_plug_t ntlm_client_plugins[] =
{
{
"NTLM", /* mech_name */
0, /* max_ssf */
SASL_SEC_NOPLAINTEXT
| SASL_SEC_NOANONYMOUS, /* security_flags */
SASL_FEAT_WANT_CLIENT_FIRST, /* features */
NULL, /* required_prompts */
NULL, /* glob_context */
&ntlm_client_mech_new, /* mech_new */
&ntlm_client_mech_step, /* mech_step */
&ntlm_client_mech_dispose, /* mech_dispose */
NULL, /* mech_free */
NULL, /* idle */
NULL, /* spare */
NULL /* spare */
}
};
int ntlm_client_plug_init(sasl_utils_t *utils,
int maxversion,
int *out_version,
sasl_client_plug_t **pluglist,
int *plugcount)
{
if (maxversion < SASL_CLIENT_PLUG_VERSION) {
SETERROR(utils, "NTLM version mismatch");
return SASL_BADVERS;
}
*out_version = SASL_CLIENT_PLUG_VERSION;
*pluglist = ntlm_client_plugins;
*plugcount = 1;
return SASL_OK;
}