@awo00/smb2
Version:
A SMB2 implementation in TypeScript
227 lines (226 loc) • 8.64 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = __importDefault(require("crypto"));
const net_1 = require("net");
const events_1 = require("events");
const Packet_1 = __importDefault(require("../protocol/Packet"));
const Request_1 = __importDefault(require("../protocol/smb2/Request"));
const Response_1 = __importDefault(require("../protocol/smb2/Response"));
const StatusCode_1 = __importDefault(require("../protocol/smb2/StatusCode"));
const PacketType_1 = __importDefault(require("../protocol/smb2/PacketType"));
const Session_1 = __importDefault(require("./Session"));
const structureUtil = __importStar(require("../protocol/structureUtil"));
class Client extends events_1.EventEmitter {
host;
options;
_id = crypto_1.default.randomBytes(4).toString("hex");
socket;
nextMessageId = 0n;
responseRestChunk;
responseMap = new Map();
responseCallbackMap = new Map();
connected = false;
port = 445;
connectTimeout = 5 * 1000;
connectTimeoutId;
requestTimeout = 5 * 1000;
requestTimeoutIdMap = new Map();
sessions = [];
constructor(host, options = {}) {
super();
this.host = host;
this.options = options;
if (typeof this.options.port === "number")
this.port = this.options.port;
if (typeof this.options.connectTimeout === "number")
this.connectTimeout = this.options.connectTimeout;
if (typeof this.options.requestTimeout === "number")
this.requestTimeout = this.options.requestTimeout;
}
async connect() {
if (this.connected)
return;
this.socket = new net_1.Socket({ allowHalfOpen: true })
.addListener("data", this.onData)
.addListener("error", this.onError)
.addListener("close", this.onClose);
this.socket.setTimeout(0);
this.socket.setKeepAlive(true);
const connectPromise = new Promise((resolve, reject) => {
this.connectTimeoutId = setTimeout(() => {
reject(new Error("connect_timeout"));
}, this.connectTimeout);
this.socket.connect(this.port, this.host);
this.socket.once("connect", () => {
resolve();
});
this.socket.once("error", (err) => {
reject(err);
});
});
try {
await connectPromise;
clearTimeout(this.connectTimeoutId);
this.connected = true;
}
catch (err) {
this.destroySocket();
throw err;
}
}
createRequest(header = {}, body = {}) {
const messageId = this.nextMessageId++;
return new Request_1.default({
messageId,
clientId: this._id,
...header
}, body);
}
async request(header, body) {
const request = this.createRequest(header, body);
return await this.send(request);
}
async send(request) {
if (!this.connected)
throw new Error("not_connected");
const buffer = request.serialize();
this.socket.write(buffer);
const messageId = request.header.messageId;
const sendPromise = new Promise((resolve, reject) => {
const requestTimeoutId = setTimeout(() => {
const err = new Error(`request_timeout: ${structureUtil.parseEnumValue(PacketType_1.default, request.header.type)}(${messageId})`);
reject(err);
}, this.requestTimeout);
this.requestTimeoutIdMap.set(messageId, requestTimeoutId);
const finishRequest = (response) => {
response.request = request;
if (response.header.status !== StatusCode_1.default.Success &&
response.header.status !== StatusCode_1.default.Pending &&
response.header.status !== StatusCode_1.default.MoreProcessingRequired &&
response.header.status !== StatusCode_1.default.FileClosed) {
reject(response);
}
else {
resolve(response);
}
};
if (this.responseMap.has(messageId)) {
finishRequest(this.responseMap.get(messageId));
this.responseMap.delete(messageId);
}
else if (!this.responseCallbackMap.has(messageId)) {
this.responseCallbackMap.set(messageId, finishRequest);
}
});
const response = await sendPromise;
if (this.requestTimeoutIdMap.has(messageId)) {
const requestTimeoutId = this.requestTimeoutIdMap.get(messageId);
clearTimeout(requestTimeoutId);
this.requestTimeoutIdMap.delete(messageId);
}
return response;
}
onData = (buffer) => {
if (this.responseRestChunk) {
buffer = Buffer.concat([this.responseRestChunk, buffer]);
this.responseRestChunk = undefined;
}
const { chunks, restChunk } = Packet_1.default.getChunks(buffer);
this.responseRestChunk = restChunk;
for (const chunk of chunks) {
const response = Response_1.default.parse(chunk);
this.onResponse(response);
}
};
onResponse(response) {
if (response.header.type === PacketType_1.default.ChangeNotify &&
response.header.status === StatusCode_1.default.Success) {
this.emit("changeNotify", response);
}
const messageId = response.header.messageId;
if (this.responseCallbackMap.has(messageId)) {
this.responseCallbackMap.get(messageId)(response);
this.responseCallbackMap.delete(messageId);
}
else {
this.responseMap.set(messageId, response);
}
}
onError = (err) => {
console.error(err);
};
onClose = (hadError) => {
this.connected = false;
};
async echo() {
return await this.request({
type: PacketType_1.default.Echo
});
}
async authenticate(options) {
if (!this.connected)
await this.connect();
const session = new Session_1.default(this);
this.registerSession(session);
await session.authenticate(options);
return session;
}
destroySocket() {
this.socket
.removeListener("data", this.onData)
.removeListener("error", this.onError)
.removeListener("close", this.onClose);
this.socket.end();
this.socket.destroy();
delete this.socket;
}
registerSession(session) {
session
.once("authenticate", () => this.sessions.push(session))
.once("logoff", () => this.sessions.splice(this.sessions.indexOf(session), 1));
}
async close() {
if (!this.connected)
return;
await Promise.all(this.sessions.map(x => x.logoff()));
this.destroySocket();
this.connected = false;
}
}
exports.default = Client;