UNPKG

node-smb-server

Version:

A Pure JavaScript SMB Server Implementation

331 lines (289 loc) 12.3 kB
/* * Copyright 2016 Adobe Systems Incorporated. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ 'use strict'; var put = require('put'); var logger = require('winston').loggers.get('default'); var _ = require('lodash'); var utils = require('./utils'); var consts = {}; consts.NTLMSSP_SIGNATURE = new Buffer('NTLMSSP\0', 'ascii'); consts.NTLMSSP_NEGOTIATE_MESSAGE = 1; consts.NTLMSSP_CHALLENGE_MESSAGE = 2; consts.NTLMSSP_AUTHENTICATE_MESSAGE = 3; consts.MESSAGE_TYPE_TO_STRING = { 1: 'NTLMSSP_NEGOTIATE_MESSAGE', 2: 'NTLMSSP_CHALLENGE_MESSAGE', 3: 'NTLMSSP_AUTHENTICATE_MESSAGE' }; consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH = 16; consts.NTLMSSP_AUTHENTICATE_MIN_MSG_LENGTH = 88; consts.NTLMSSP_REVISION_W2K3 = 0x0f; // NTLMSSP Flags consts.NTLMSSP_NEGOTIATE_UNICODE = 0x00000001; consts.NTLMSSP_NEGOTIATE_OEM = 0x00000002; consts.NTLMSSP_REQUEST_TARGET = 0x00000004; consts.NTLMSSP_RESERVED_10 = 0x00000008; consts.NTLMSSP_NEGOTIATE_SIGN = 0x00000010; consts.NTLMSSP_NEGOTIATE_SEAL = 0x00000020; consts.NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040; consts.NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080; consts.NTLMSSP_RESERVED_9 = 0x00000100; consts.NTLMSSP_NEGOTIATE_NTLM = 0x00000200; consts.NTLMSSP_RESERVED_8 = 0x00000400; consts.NTLMSSP_NEGOTIATE_ANONYMOUS = 0x00000800; consts.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000; consts.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000; consts.NTLMSSP_RESERVED_7 = 0x00004000; consts.NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000; consts.NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000; consts.NTLMSSP_TARGET_TYPE_SERVER = 0x00020000; consts.NTLMSSP_RESERVED_6 = 0x00040000; consts.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000; consts.NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000; consts.NTLMSSP_RESERVED_5 = 0x00200000; consts.NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000; consts.NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000; consts.NTLMSSP_RESERVED_4 = 0x01000000; consts.NTLMSSP_NEGOTIATE_VERSION = 0x02000000; consts.NTLMSSP_RESERVED_3 = 0x04000000; consts.NTLMSSP_RESERVED_2 = 0x08000000; consts.NTLMSSP_RESERVED_1 = 0x10000000; consts.NTLMSSP_NEGOTIATE_128 = 0x20000000; consts.NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000; consts.NTLMSSP_NEGOTIATE_56 = 0x80000000; // AV Pair Field IDs consts.NTLMSSP_AV_EOL = 0x0000; consts.NTLMSSP_AV_NB_COMPUTER_NAME = 0x0001; consts.NTLMSSP_AV_NB_DOMAIN_NAME = 0x0002; consts.NTLMSSP_AV_DNS_COMPUTER_NAME = 0x0003; consts.NTLMSSP_AV_DNS_DOMAIN_NAME = 0x0004; consts.NTLMSSP_AV_DNS_TREE_NAME = 0x0005; consts.NTLMSSP_AV_FLAGS = 0x0006; consts.NTLMSSP_AV_TIMESTAMP = 0x0007; consts.NTLMSSP_AV_RESTRICTION = 0x0008; consts.NTLMSSP_AV_TARGET_NAME = 0x0009; consts.NTLMSSP_AV_CHANNEL_BINDINGS = 0x000a; function parseMessageType(buf) { if (buf.length < consts.NTLMSSP_SIGNATURE.length + 4) { logger.warn('invalid NTLMSSP message: expected length: >=%d, actual length: %d, data: 0x%s', consts.NTLMSSP_SIGNATURE.length + 4, buf.length, buf.toString('hex')); return -1; } var off = 0; var sig = buf.slice(off, consts.NTLMSSP_SIGNATURE.length); off += consts.NTLMSSP_SIGNATURE.length; if (!utils.bufferEquals(consts.NTLMSSP_SIGNATURE, sig)) { logger.warn('invalid NTLMSSP message signature: data: 0x%s', sig.toString('hex')); return -1; } // type return buf.readUInt32LE(off); } function parseNegotiateMessage(buf) { var type = parseMessageType(buf); if (type !== consts.NTLMSSP_NEGOTIATE_MESSAGE) { logger.warn('invalid NTLMSSP message type: expected: %s, actual: %s', consts.MESSAGE_TYPE_TO_STRING[consts.NTLMSSP_NEGOTIATE_MESSAGE], consts.MESSAGE_TYPE_TO_STRING[type]); return null; } if (buf.length < consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH) { logger.warn('invalid NTLMSSP_NEGOTIATE message: expected length: >=%d, actual length: %d, data: 0x%s', consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH, buf.length, buf.toString('hex')); return null; } var off = consts.NTLMSSP_SIGNATURE.length + 4; var flags = buf.readUInt32LE(off); off += 4; var msg = { type: type, flags: flags }; // minimal message size (16 bytes) includes just signature, type and flags if (buf.length === consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH) { return msg; } if (buf.length < consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH + 8 + 8) { logger.warn('invalid NTLMSSP_NEGOTIATE message: expected length: >=%d, actual length: %d, data: 0x%s', consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH + 8 + 8, buf.length, buf.toString('hex')); return null; } var domainLength = buf.readUInt16LE(off); off += 2; var domainMaxLength = buf.readUInt16LE(off); off += 2; var domainOffset = buf.readUInt32LE(off); off += 4; var workstationLength = buf.readUInt16LE(off); off += 2; var workstationMaxLength = buf.readUInt16LE(off); off += 2; var workstationOffset = buf.readUInt32LE(off); off += 4; // OS Version structure is optional if (off != domainOffset && off != workstationOffset && buf.length >= consts.NTLMSSP_NEGOTIATE_MIN_MSG_LENGTH + 8 + 8 + 8) { var productMajorVersion = buf.readUInt8(off); off += 1; var productMinorVersion = buf.readUInt8(off); off += 1; var productBuild = buf.readUInt16LE(off); off += 2; off += 3; // reserved var ntlmRevisionCurrent = buf.readUInt8(off); off += 1; } if (flags & consts.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED && domainOffset) { if (domainOffset + domainLength > buf.length) { logger.warn('invalid NTLMSSP message: domainOffset: %d, domainLength: %d, msgLength: %s', domainOffset, domainLength, buf.length); return null; } msg.domain = buf.slice(domainOffset, domainOffset + domainLength).toString('ascii'); } if (flags & consts.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED && workstationOffset) { if (workstationOffset + workstationLength > buf.length) { logger.warn('invalid NTLMSSP message: workstationOffset: %d, workstationLength: %d, msgLength: %s', workstationOffset, workstationLength, buf.length); return null; } msg.workstation = buf.slice(workstationOffset, workstationOffset + workstationLength).toString('ascii'); } return msg; } function createChallengeMessage(negotiateFlags, challenge, targetName, domainName) { var computerName = targetName.split('.')[0].toUpperCase(); var dnsDomain = targetName.split('.').slice(1).join('.'); var targetNameLen = negotiateFlags & consts.NTLMSSP_REQUEST_TARGET ? computerName.length * 2 : 0; var targetNameOffset = 56; var smbTime = utils.systemToSMBTime(Date.now()); var info = put(); info.word16le(consts.NTLMSSP_AV_NB_COMPUTER_NAME) .word16le(computerName.length * 2) .put(new Buffer(computerName.toUpperCase(), 'utf16le')) .word16le(consts.NTLMSSP_AV_NB_DOMAIN_NAME) .word16le(domainName.length * 2) .put(new Buffer(domainName.toUpperCase(), 'utf16le')) .word16le(consts.NTLMSSP_AV_DNS_COMPUTER_NAME) .word16le(targetName.length * 2) .put(new Buffer(targetName, 'utf16le')) .word16le(consts.NTLMSSP_AV_DNS_DOMAIN_NAME) .word16le(dnsDomain.length * 2) .put(new Buffer(dnsDomain, 'utf16le')) .word16le(consts.NTLMSSP_AV_TIMESTAMP) .word16le(8) .word32le(smbTime.getLowBits()) .word32le(smbTime.getHighBits()) .word16le(consts.NTLMSSP_AV_EOL) // MsvAvEOL .word16le(0); // force target info (windows clients seem to require it) negotiateFlags |= consts.NTLMSSP_NEGOTIATE_TARGET_INFO; var targetInfoLen = negotiateFlags & consts.NTLMSSP_NEGOTIATE_TARGET_INFO ? info.length() : 0; var targetInfoOffset = targetNameOffset + targetNameLen; var supportedFlags = consts.NTLMSSP_NEGOTIATE_128 | consts.NTLMSSP_NEGOTIATE_VERSION | consts.NTLMSSP_NEGOTIATE_NTLM | consts.NTLMSSP_REQUEST_TARGET | consts.NTLMSSP_NEGOTIATE_TARGET_INFO | consts.NTLMSSP_NEGOTIATE_UNICODE; var flags = negotiateFlags & supportedFlags; flags |= consts.NTLMSSP_TARGET_TYPE_SERVER; var out = put(); out.put(consts.NTLMSSP_SIGNATURE) // Signature .word32le(consts.NTLMSSP_CHALLENGE_MESSAGE) // MessageType .word16le(targetNameLen) // TargetNameLen .word16le(targetNameLen) // TargetNameMaxLen .word32le(targetNameOffset) // TargetNameBufferOffset .word32le(flags) // NegotiateFlags .put(challenge) // ServerChallenge .pad(8) // Reserved .word16le(targetInfoLen) // TargetInfoLen .word16le(targetInfoLen) // TargetInfoMaxLen .word32le(targetInfoOffset) // TargetInfoBufferOffset .word8(0x06) // ProductMajorVersion .word8(0x01) // ProductMajorVersion .word16le(7600) // ProductBuild .pad(3) // Reserved .word8(consts.NTLMSSP_REVISION_W2K3) // NTLMRevisionCurrent .put(new Buffer(computerName, 'utf16le')) // TargetName .put(info.buffer()); // TargetInfo return out.buffer(); } function parseAuthenticateMessage(buf) { var type = parseMessageType(buf) if (type !== consts.NTLMSSP_AUTHENTICATE_MESSAGE) { logger.warn('invalid NTLMSSP message type: expected: %s, actual: %s', consts.MESSAGE_TYPE_TO_STRING[consts.NTLMSSP_AUTHENTICATE_MESSAGE], consts.MESSAGE_TYPE_TO_STRING[type]); return null; } if (buf.length < consts.NTLMSSP_AUTHENTICATE_MIN_MSG_LENGTH) { logger.warn('invalid NTLMSSP_AUTHENTICATE message: expected length: >=%d, actual length: %d, data: 0x%s', consts.NTLMSSP_AUTHENTICATE_MIN_MSG_LENGTH, buf.length, buf.toString('hex')); return null; } var off = consts.NTLMSSP_SIGNATURE.length + 4; var lmResponseLength = buf.readUInt16LE(off); off += 2; var lmResponseMaxLength = buf.readUInt16LE(off); off += 2; var lmResponseOffset = buf.readUInt32LE(off); off += 4; var ntResponseLength = buf.readUInt16LE(off); off += 2; var ntResponseMaxLength = buf.readUInt16LE(off); off += 2; var ntResponseOffset = buf.readUInt32LE(off); off += 4; var domainLength = buf.readUInt16LE(off); off += 2; var domainMaxLength = buf.readUInt16LE(off); off += 2; var domainOffset = buf.readUInt32LE(off); off += 4; var userLength = buf.readUInt16LE(off); off += 2; var userMaxLength = buf.readUInt16LE(off); off += 2; var userOffset = buf.readUInt32LE(off); off += 4; var workstationLength = buf.readUInt16LE(off); off += 2; var workstationMaxLength = buf.readUInt16LE(off); off += 2; var workstationOffset = buf.readUInt32LE(off); off += 4; var sessionKeyLength = buf.readUInt16LE(off); off += 2; var sessionKeyMaxLength = buf.readUInt16LE(off); off += 2; var sessionKeyOffset = buf.readUInt32LE(off); off += 4; var flags = buf.readUInt32LE(off); off += 4; var productMajorVersion = buf.readUInt8(off); off += 1; var productMinorVersion = buf.readUInt8(off); off += 1; var productBuild = buf.readUInt16LE(off); off += 2; off += 3; // reserved var ntlmRevisionCurrent = buf.readUInt8(off); off += 1; var mic = buf.slice(off, off + 16); off += 16; var msg = { type: type, flags: flags, mic: mic }; msg.lmResponse = buf.slice(lmResponseOffset, lmResponseOffset + lmResponseLength); msg.ntResponse = buf.slice(ntResponseOffset, ntResponseOffset + ntResponseLength); msg.user = buf.slice(userOffset, userOffset + userLength).toString('utf16le'); msg.domain = buf.slice(domainOffset, domainOffset + domainLength).toString('utf16le'); msg.workstation = buf.slice(workstationOffset, workstationOffset + workstationLength).toString('utf16le'); return msg; } _.assign(module.exports, consts); module.exports.parseMessageType = parseMessageType; module.exports.parseNegotiateMessage = parseNegotiateMessage; module.exports.createChallengeMessage = createChallengeMessage; module.exports.parseAuthenticateMessage = parseAuthenticateMessage;