Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299/** * SMB2 Close Command Handler * Handles SMB2_CLOSE requests and responses for closing files and directories */
import type { SMB2Connection, SMB2Session, SMB2TreeConnect, SMB2CloseRequest, SMB2CloseResponse, SMB2CommandHandler, SMB2FileId, SMB2OpenFile,} from '../protocol/types.js';import { SMB2_STATUS, SMB2_HEADER_SIZE, SMB2_COMMANDS, FILE_ATTRIBUTES,} from '../protocol/constants.js';import { SMB2MessageParser } from '../protocol/parser.js';import { SMB2MessageSerializer } from '../protocol/serializer.js';import { SMB2FileSystemProvider } from '../filesystem/provider.js';import { Logger } from '../utils/logger.js';import { fileIdToString, getCurrentFileTime } from '../utils/helpers.js';
export class SMB2CloseHandler { private readonly fileSystemProvider: SMB2FileSystemProvider; private readonly parser: SMB2MessageParser; private readonly serializer: SMB2MessageSerializer; private readonly logger: Logger;
constructor(fileSystemProvider: SMB2FileSystemProvider) { this.fileSystemProvider = fileSystemProvider; this.parser = new SMB2MessageParser(); this.serializer = new SMB2MessageSerializer(); this.logger = new Logger('SMB2CloseHandler'); }
public async handle( requestData: Buffer, connection: SMB2Connection, session?: SMB2Session, treeConnect?: SMB2TreeConnect, ): Promise<Buffer> { try { // Validate session and tree connect if (!session) { this.logger.warn('Close attempt without valid session'); throw new Error('No valid session'); }
if (!treeConnect) { this.logger.warn('Close attempt without valid tree connect'); throw new Error('No valid tree connect'); }
// Parse the close request const parseResult = this.parser.parseCloseRequest(requestData); const request = parseResult.result;
const fileIdStr = fileIdToString(request.fileId); this.logger.debug(`Close request from ${connection.clientAddress}:`); this.logger.debug(` File ID: ${fileIdStr}`); this.logger.debug(` Flags: 0x${request.flags.toString(16)}`);
// Validate request this.validateCloseRequest(request);
// Find the open file const openFile = connection.openFiles.get(fileIdStr); if (!openFile) { this.logger.warn(`Attempted to close non-existent file: ${fileIdStr}`); throw new Error('File not open'); }
// Verify the file belongs to the correct session and tree if (openFile.sessionId !== session.sessionId) { this.logger.warn(`File ${fileIdStr} does not belong to session ${session.sessionId}`); throw new Error('Invalid session for file'); }
if (openFile.treeConnectId !== treeConnect.treeConnectId) { this.logger.warn(`File ${fileIdStr} does not belong to tree ${treeConnect.treeConnectId}`); throw new Error('Invalid tree connect for file'); }
// Get file information before closing (if requested by flags) let fileInfo = null; if (request.flags & 0x0001) { // SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB try { fileInfo = await this.fileSystemProvider.getFileInfo(request.fileId); } catch (error) { this.logger.warn(`Failed to get file info before close: ${error}`); // Continue with close even if we can't get file info } }
try { // Close the file through filesystem provider await this.fileSystemProvider.closeFile(request.fileId); } catch (error) { this.logger.error(`Error closing file ${fileIdStr}:`, error); // Continue to remove from tracking even if close failed }
// Remove from connection's open files tracking connection.openFiles.delete(fileIdStr);
// Create response with file information if requested and available const currentTime = getCurrentFileTime(); const response: SMB2CloseResponse = { structureSize: 60, flags: request.flags, reserved: 0, creationTime: fileInfo?.basic.creationTime || currentTime, lastAccessTime: fileInfo?.basic.lastAccessTime || currentTime, lastWriteTime: fileInfo?.basic.lastWriteTime || currentTime, changeTime: fileInfo?.basic.changeTime || currentTime, allocationSize: fileInfo?.standard.allocationSize || 0n, endOfFile: fileInfo?.standard.endOfFile || 0n, fileAttributes: fileInfo?.basic.fileAttributes || FILE_ATTRIBUTES.NORMAL, };
// Serialize the response const responseBuffer = this.serializer.serializeCloseResponse(response);
this.logger.info(`File closed successfully: ${openFile.fileName} (FileID: ${fileIdStr})`); return responseBuffer;
} catch (error) { this.logger.error('Error handling close request:', error); throw error; } }
/** * Validate close request parameters */ private validateCloseRequest(request: SMB2CloseRequest): void { if (request.structureSize !== 24) { throw new Error(`Invalid structure size: ${request.structureSize}`); }
// Validate file ID is not null if (request.fileId.persistent === 0n && request.fileId.volatile === 0n) { throw new Error('Invalid file ID (null file ID)'); }
// Validate flags (only known flags are allowed) const validFlags = 0x0001; // SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB if ((request.flags & ~validFlags) !== 0) { this.logger.warn(`Unknown close flags: 0x${request.flags.toString(16)}`); } }
/** * Force close all files for a session */ public async forceCloseSessionFiles( connection: SMB2Connection, sessionId: bigint ): Promise<void> { const filesToClose: string[] = []; // Find all files belonging to the session for (const [fileIdStr, openFile] of connection.openFiles.entries()) { if (openFile.sessionId === sessionId) { filesToClose.push(fileIdStr); } }
// Close each file for (const fileIdStr of filesToClose) { try { const openFile = connection.openFiles.get(fileIdStr); if (openFile) { await this.fileSystemProvider.closeFile(openFile.fileId); connection.openFiles.delete(fileIdStr); this.logger.debug(`Force closed file: ${openFile.fileName} (${fileIdStr})`); } } catch (error) { this.logger.warn(`Error force closing file ${fileIdStr}:`, error); } }
if (filesToClose.length > 0) { this.logger.info(`Force closed ${filesToClose.length} files for session ${sessionId}`); } }
/** * Force close all files for a tree connect */ public async forceCloseTreeFiles( connection: SMB2Connection, treeConnectId: number ): Promise<void> { const filesToClose: string[] = []; // Find all files belonging to the tree connect for (const [fileIdStr, openFile] of connection.openFiles.entries()) { if (openFile.treeConnectId === treeConnectId) { filesToClose.push(fileIdStr); } }
// Close each file for (const fileIdStr of filesToClose) { try { const openFile = connection.openFiles.get(fileIdStr); if (openFile) { await this.fileSystemProvider.closeFile(openFile.fileId); connection.openFiles.delete(fileIdStr); this.logger.debug(`Force closed file: ${openFile.fileName} (${fileIdStr})`); } } catch (error) { this.logger.warn(`Error force closing file ${fileIdStr}:`, error); } }
if (filesToClose.length > 0) { this.logger.info(`Force closed ${filesToClose.length} files for tree ${treeConnectId}`); } }
/** * Force close all files for a connection */ public async forceCloseConnectionFiles(connection: SMB2Connection): Promise<void> { const filesToClose = Array.from(connection.openFiles.keys());
// Close each file for (const fileIdStr of filesToClose) { try { const openFile = connection.openFiles.get(fileIdStr); if (openFile) { await this.fileSystemProvider.closeFile(openFile.fileId); connection.openFiles.delete(fileIdStr); this.logger.debug(`Force closed file: ${openFile.fileName} (${fileIdStr})`); } } catch (error) { this.logger.warn(`Error force closing file ${fileIdStr}:`, error); } }
if (filesToClose.length > 0) { this.logger.info(`Force closed ${filesToClose.length} files for connection ${connection.connectionId}`); } }
/** * Get information about open files */ public getOpenFilesInfo(connection: SMB2Connection): Array<{ fileId: string; fileName: string; sessionId: bigint; treeConnectId: number; openTime: Date; }> { const openFiles: Array<{ fileId: string; fileName: string; sessionId: bigint; treeConnectId: number; openTime: Date; }> = [];
for (const [fileIdStr, openFile] of connection.openFiles.entries()) { openFiles.push({ fileId: fileIdStr, fileName: openFile.fileName, sessionId: openFile.sessionId, treeConnectId: openFile.treeConnectId, openTime: openFile.creationTime, }); }
return openFiles; }
/** * Check if a file is open */ public isFileOpen(connection: SMB2Connection, fileId: SMB2FileId): boolean { const fileIdStr = fileIdToString(fileId); return connection.openFiles.has(fileIdStr); }
/** * Get open file information */ public getOpenFile(connection: SMB2Connection, fileId: SMB2FileId): SMB2OpenFile | undefined { const fileIdStr = fileIdToString(fileId); return connection.openFiles.get(fileIdStr); }}