diff --git a/Sources/SMBClient/Connection.swift b/Sources/SMBClient/Connection.swift index dd41ea8..cfcba07 100644 --- a/Sources/SMBClient/Connection.swift +++ b/Sources/SMBClient/Connection.swift @@ -158,14 +158,14 @@ public class Connection { repeat { header = reader.read() - switch header.status { + switch NTStatus(header.status) { case - NTStatus.success, - NTStatus.moreProcessingRequired, - NTStatus.noMoreFiles, - NTStatus.endOfFile: + .success, + .moreProcessingRequired, + .noMoreFiles, + .endOfFile: response += data - case NTStatus.pending: + case .pending: if self.buffer.count > 0 { let transportPacket = DirectTCPPacket(response: self.buffer) let length = Int(transportPacket.protocolLength) @@ -181,12 +181,12 @@ public class Connection { let reader = ByteReader(data) let header: Header = reader.read() - switch header.status { + switch NTStatus(header.status) { case - NTStatus.success, - NTStatus.moreProcessingRequired, - NTStatus.noMoreFiles, - NTStatus.endOfFile: + .success, + .moreProcessingRequired, + .noMoreFiles, + .endOfFile: response += data break default: diff --git a/Sources/SMBClient/FileReader.swift b/Sources/SMBClient/FileReader.swift index 4e5e25f..4b54c3b 100644 --- a/Sources/SMBClient/FileReader.swift +++ b/Sources/SMBClient/FileReader.swift @@ -37,7 +37,7 @@ public class FileReader { ) buffer.append(response.buffer) - } while response.header.status != NTStatus.endOfFile && buffer.count < length && offset + UInt64(buffer.count) < fileProxy.size + } while NTStatus(response.header.status) != .endOfFile && buffer.count < length && offset + UInt64(buffer.count) < fileProxy.size return buffer } @@ -57,7 +57,7 @@ public class FileReader { buffer.append(response.buffer) offset = UInt64(buffer.count) - } while response.header.status != NTStatus.endOfFile && buffer.count < fileProxy.size + } while NTStatus(response.header.status) != .endOfFile && buffer.count < fileProxy.size return buffer } diff --git a/Sources/SMBClient/Messages/ErrorCodes.swift b/Sources/SMBClient/Messages/ErrorCodes.swift deleted file mode 100644 index eee9914..0000000 --- a/Sources/SMBClient/Messages/ErrorCodes.swift +++ /dev/null @@ -1,130 +0,0 @@ -import Foundation - -public typealias NTStatus = ErrorCodes - -public enum ErrorCodes { - public static let success: UInt32 = 0x00000000 - public static let pending: UInt32 = 0x00000103 - public static let invalidSMB: UInt32 = 0x00010002 - public static let smbBadTid: UInt32 = 0x00050002 - public static let smbBadCommand: UInt32 = 0x00160002 - public static let smbBadUID: UInt32 = 0x005B0002 - public static let smbUseStandard: UInt32 = 0x00FB0002 - public static let bufferOverflow: UInt32 = 0x80000005 - public static let noMoreFiles: UInt32 = 0x80000006 - public static let stoppedOnSymlink: UInt32 = 0x8000002D - public static let notImplemented: UInt32 = 0xC0000002 - public static let invalidInfoClass: UInt32 = 0xC0000003 - public static let invalidParameter: UInt32 = 0xC000000D - public static let noSuchDevice: UInt32 = 0xC000000E - public static let noSuchFile: UInt32 = 0xC000000F - public static let invalidDeviceRequest: UInt32 = 0xC0000010 - public static let endOfFile: UInt32 = 0xC0000011 - public static let moreProcessingRequired: UInt32 = 0xC0000016 - public static let accessDenied: UInt32 = 0xC0000022 - public static let bufferTooSmall: UInt32 = 0xC0000023 - public static let objectNameInvalid: UInt32 = 0xC0000033 - public static let objectNameNotFound: UInt32 = 0xC0000034 - public static let objectNameCollision: UInt32 = 0xC0000035 - public static let sharingViolation: UInt32 = 0xC0000043 - public static let deletePending: UInt32 = 0xC0000056 - public static let objectPathNotFound: UInt32 = 0xC000003A - public static let logonFailure: UInt32 = 0xC000006D - public static let badImpersonationLevel: UInt32 = 0xC00000A5 - public static let ioTimeout: UInt32 = 0xC00000B5 - public static let fileIsADirectory: UInt32 = 0xC00000BA - public static let notSupported: UInt32 = 0xC00000BB - public static let networkNameDeleted: UInt32 = 0xC00000C9 - public static let badNetworkName: UInt32 = 0xC00000CC - public static let notADirectory: UInt32 = 0xC0000103 - public static let fileClosed: UInt32 = 0xC0000128 - public static let userSessionDeleted: UInt32 = 0xC0000203 - public static let connectionRefused: UInt32 = 0xC0000236 - public static let networkSessionExpired: UInt32 = 0xC000035C - public static let smbTooManyUIDs: UInt32 = 0xC000205A - - public static func description(_ code: UInt32) -> String { - switch code { - case ErrorCodes.success: - return "The client request is successful." - case ErrorCodes.pending: - return "The operation that was requested is pending completion." - case ErrorCodes.invalidSMB: - return "An invalid SMB client request is received by the server." - case ErrorCodes.smbBadTid: - return "The client request received by the server contains an invalid TID value." - case ErrorCodes.smbBadCommand: - return "The client request received by the server contains an unknown SMB command code." - case ErrorCodes.smbBadUID: - return "The client request to the server contains an invalid UID value." - case ErrorCodes.smbUseStandard: - return "The client request received by the server is for a non-standard SMB operation (for example, an SMB_COM_READ_MPX request on a non-disk share). The client SHOULD send another request with a different SMB command to perform this operation." - case ErrorCodes.bufferOverflow: - return "The data was too large to fit into the specified buffer." - case ErrorCodes.noMoreFiles: - return "No more files were found that match the file specification." - case ErrorCodes.stoppedOnSymlink: - return "The create operation stopped after reaching a symbolic link." - case ErrorCodes.notImplemented: - return "The requested operation is not implemented." - case ErrorCodes.invalidInfoClass: - return "The specified information class is not a valid information class for the specified object." - case ErrorCodes.invalidParameter: - return "The parameter specified in the request is not valid." - case ErrorCodes.noSuchFile: - return "File not found." - case ErrorCodes.noSuchDevice: - return "A device that does not exist was specified." - case ErrorCodes.invalidDeviceRequest: - return "The specified request is not a valid operation for the target device." - case ErrorCodes.endOfFile: - return "The end-of-file marker has been reached. There is no valid data in the file beyond this marker." - case ErrorCodes.moreProcessingRequired: - return "If extended security has been negotiated, then this error code can be returned in the SMB_COM_SESSION_SETUP_ANDX response from the server to indicate that additional authentication information is to be exchanged. See section 2.2.4.6 for details." - case ErrorCodes.accessDenied: - return "The client did not have the required permission needed for the operation." - case ErrorCodes.bufferTooSmall: - return "The buffer is too small to contain the entry. No information has been written to the buffer." - case ErrorCodes.objectNameInvalid: - return "The object name is invalid." - case ErrorCodes.objectNameNotFound: - return "The object name is not found." - case ErrorCodes.objectNameCollision: - return "The object name already exists." - case ErrorCodes.sharingViolation: - return "A file cannot be opened because the share access flags are incompatible." - case ErrorCodes.deletePending: - return "A non-close operation has been requested of a file object that has a delete pending." - case ErrorCodes.objectPathNotFound: - return "The path to the directory specified was not found. This error is also returned on a create request if the operation requires the creation of more than one new directory level for the path specified." - case ErrorCodes.logonFailure: - return "The attempted logon is invalid. This is either due to a bad username or authentication information." - case ErrorCodes.badImpersonationLevel: - return "A specified impersonation level is invalid. This error is also used to indicate that a required impersonation level was not provided." - case ErrorCodes.ioTimeout: - return "The specified I/O operation was not completed before the time-out period expired." - case ErrorCodes.fileIsADirectory: - return "The file that was specified as a target is a directory and the caller specified that it could be anything but a directory." - case ErrorCodes.notSupported: - return "The client request is not supported." - case ErrorCodes.networkNameDeleted: - return "The network name specified by the client has been deleted on the server. This error is returned if the client specifies an incorrect TID or the share on the server represented by the TID was deleted." - case ErrorCodes.badNetworkName: - return "The specified share name cannot be found on the remote server." - case ErrorCodes.notADirectory: - return "A requested opened file is not a directory." - case ErrorCodes.fileClosed: - return "An I/O request other than close and several other special case operations was attempted using a file object that had already been closed." - case ErrorCodes.userSessionDeleted: - return "The user session specified by the client has been deleted on the server. This error is returned by the server if the client sends an incorrect UID." - case ErrorCodes.connectionRefused: - return "The transport-connection attempt was refused by the remote system." - case ErrorCodes.networkSessionExpired: - return "The client's session has expired; therefore, the client MUST re-authenticate to continue accessing remote resources." - case ErrorCodes.smbTooManyUIDs: - return "The client has requested too many UID values from the server or the client already has an SMB session setup with this UID value." - default: - return "Unknown error: \(String(format: "0x%08X", code))" - } - } -} diff --git a/Sources/SMBClient/Messages/ErrorResponse.swift b/Sources/SMBClient/Messages/ErrorResponse.swift index 195e470..7d63940 100644 --- a/Sources/SMBClient/Messages/ErrorResponse.swift +++ b/Sources/SMBClient/Messages/ErrorResponse.swift @@ -18,7 +18,7 @@ public struct ErrorResponse: Error { extension ErrorResponse: CustomStringConvertible { public var description: String { - return ErrorCodes.description(header.status) + return NTStatus(header.status).description } } diff --git a/Sources/SMBClient/Messages/Header+CustomDebugStringConvertible.swift b/Sources/SMBClient/Messages/Header+CustomDebugStringConvertible.swift new file mode 100644 index 0000000..c90d829 --- /dev/null +++ b/Sources/SMBClient/Messages/Header+CustomDebugStringConvertible.swift @@ -0,0 +1,113 @@ +import Foundation + +extension Header: CustomDebugStringConvertible { + public var debugDescription: String { + if flags.contains(.serverToRedir) { + return + """ + SMB2 Header + ProtocolId: \(String(format: "0x%08x", protocolId.bigEndian)) + Header Length: \(structureSize) + Credit Charge: \(creditCharge) + NT Status: \(NTStatus(status).debugDescription) (\(String(format: "0x%08x", status))) + Command: \(Command.debugDescription(rawValue: command)) (\(command)) + Credits granted: \(creditRequestResponse) + Flags: \(flags) + Chain Offset: \(nextCommand) + Message ID: \(String(messageId, radix: 16)) + Process Id: \(String(format: "0x%08x", reserved)) + Tree Id: \(String(format: "0x%08x", treeId)) + Session Id: \(String(format: "0x%016llx", sessionId)) + Signature: \(signature.hex) + """ + } else { + return + """ + SMB2 Header + ProtocolId: \(String(format: "0x%08x", protocolId.bigEndian)) + Header Length: \(structureSize) + Credit Charge: \(creditCharge) + Channel Sequence: \(String((status & 0xFFFF0000) >> 16, radix: 16)) + Reserved: \(String(format: "%04x", status & 0x0000FFFF)) + Command: \(Command.debugDescription(rawValue: command)) (\(command)) + Credits requested: \(creditRequestResponse) + Flags: \(flags) + Chain Offset: \(nextCommand) + Message ID: \(String(messageId, radix: 16)) + Process Id: \(String(format: "0x%08x", reserved)) + Tree Id: \(String(format: "0x%08x", treeId)) + Session Id: \(String(format: "0x%016llx", sessionId)) + Signature: \(signature.hex) + """ + } + } +} + +extension Header.Command: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .negotiate: return "Negotiate Protocol" + case .sessionSetup: return "SESSION_SETUP" + case .logoff: return "LOGOFF" + case .treeConnect: return "TREE_CONNECT" + case .treeDisconnect: return "TREE_DISCONNECT" + case .create: return "CREATE" + case .close: return "CLOSE" + case .flush: return "FLUSH" + case .read: return "READ" + case .write: return "WRITE" + case .lock: return "LOCK" + case .ioctl: return "IOCTL" + case .cancel: return "CANCEL" + case .echo: return "ECHO" + case .queryDirectory: return "QUERY_DIRECTORY" + case .changeNotify: return "CHANGE_NOTIFY" + case .queryInfo: return "QUERY_INFO" + case .setInfo: return "SET_INFO" + case .oplockBreak: return "OPLOCK_BREAK" + case .serverToClientNotification: return "SERVER_TO_CLIENT_NOTIFICATION" + } + } + + static public func debugDescription(rawValue: UInt16) -> String { + if let command = Header.Command(rawValue: rawValue) { + return command.debugDescription + } else { + return String(format: "0x%04x", rawValue) + } + } +} + +extension Header.Flags: CustomDebugStringConvertible { + public var debugDescription: String { + var values: [String] = [] + + if contains(.serverToRedir) { + values.append("Response") + } + if contains(.asyncCommand) { + values.append("Async command") + } + if contains(.relatedOperations) { + values.append("Chained") + } + if contains(.signed) { + values.append("Signing") + } + if contains(.priorityMask) { + values.append("Priority") + } + if contains(.dfsOperation) { + values.append("DFS operation") + } + if contains(.replayOperation) { + values.append("Replay operation") + } + + if values.isEmpty { + return String(format: "0x%08x", rawValue) + } else { + return "\(String(format: "0x%08x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} diff --git a/Sources/SMBClient/Messages/Header.swift b/Sources/SMBClient/Messages/Header.swift index e74e2f1..ca05a7f 100644 --- a/Sources/SMBClient/Messages/Header.swift +++ b/Sources/SMBClient/Messages/Header.swift @@ -123,100 +123,3 @@ extension ByteReader { Header(data: read(count: 64)) } } - -extension Header: CustomDebugStringConvertible { - public var debugDescription: String { - if flags.contains(.serverToRedir) { - return - """ - SMB2 Header - ProtocolId: 0x\(String(format: "%08x", protocolId.bigEndian)) - Credit Charge: \(creditCharge) - NT Status: 0x\(String(format: "%08x", status)) - Command: \(Command.debugDescription(command)) (\(command)) - Credits granted: \(creditRequestResponse) - Flags: 0x\(String(format: "%08x", flags.rawValue)) (\(flags)) - Chain Offset: \(nextCommand) - Message ID: \(String(messageId, radix: 16)) - Process Id: 0x\(String(format: "%08x", reserved)) - Tree Id: 0x\(String(format: "%08x", treeId)) - Session Id: 0x\(String(format: "%016llx", sessionId)) - Signature: \(signature.hex) - """ - } else { - return - """ - SMB2 Header - ProtocolId: 0x\(String(format: "%08x", protocolId.bigEndian)) - Credit Charge: \(creditCharge) - Channel Sequence: \(String((status & 0xFFFF0000) >> 16, radix: 16)) - Reserved: \(String(format: "%04x", status & 0x0000FFFF)) - Command: \(Command.debugDescription(command)) (\(command)) - Credits requested: \(creditRequestResponse) - Flags: 0x\(String(format: "%08x", flags.rawValue)) (\(flags)) - Chain Offset: \(nextCommand) - Message ID: \(String(messageId, radix: 16)) - Process Id: 0x\(String(format: "%08x", reserved)) - Tree Id: 0x\(String(format: "%08x", treeId)) - Session Id: 0x\(String(format: "%016llx", sessionId)) - Signature: \(signature.hex) - """ - } - } -} - -extension Header.Command { - static func debugDescription(_ rawValue: UInt16) -> String { - switch rawValue { - case Self.negotiate.rawValue: return "NEGOTIATE" - case Self.sessionSetup.rawValue: return "SESSION_SETUP" - case Self.logoff.rawValue: return "LOGOFF" - case Self.treeConnect.rawValue: return "TREE_CONNECT" - case Self.treeDisconnect.rawValue: return "TREE_DISCONNECT" - case Self.create.rawValue: return "CREATE" - case Self.close.rawValue: return "CLOSE" - case Self.flush.rawValue: return "FLUSH" - case Self.read.rawValue: return "READ" - case Self.write.rawValue: return "WRITE" - case Self.lock.rawValue: return "LOCK" - case Self.ioctl.rawValue: return "IOCTL" - case Self.cancel.rawValue: return "CANCEL" - case Self.echo.rawValue: return "ECHO" - case Self.queryDirectory.rawValue: return "QUERY_DIRECTORY" - case Self.changeNotify.rawValue: return "CHANGE_NOTIFY" - case Self.queryInfo.rawValue: return "QUERY_INFO" - case Self.setInfo.rawValue: return "SET_INFO" - case Self.oplockBreak.rawValue: return "OPLOCK_BREAK" - case Self.serverToClientNotification.rawValue: return "SERVER_TO_CLIENT_NOTIFICATION" - default: return "UNKNOWN" - } - } -} - -extension Header.Flags: CustomDebugStringConvertible { - public var debugDescription: String { - var flags: [String] = [] - if contains(.serverToRedir) { - flags.append("SERVER_TO_REDIR") - } - if contains(.asyncCommand) { - flags.append("ASYNC_COMMAND") - } - if contains(.relatedOperations) { - flags.append("RELATED_OPERATIONS") - } - if contains(.signed) { - flags.append("SIGNED") - } - if contains(.priorityMask) { - flags.append("PRIORITY_MASK") - } - if contains(.dfsOperation) { - flags.append("DFS_OPERATION") - } - if contains(.replayOperation) { - flags.append("REPLAY_OPERATION") - } - return flags.joined(separator: "|") - } -} diff --git a/Sources/SMBClient/Messages/NTStatus.swift b/Sources/SMBClient/Messages/NTStatus.swift new file mode 100644 index 0000000..b62bd50 --- /dev/null +++ b/Sources/SMBClient/Messages/NTStatus.swift @@ -0,0 +1,245 @@ +import Foundation + +public struct NTStatus { + public var rawValue: UInt32 + + public init(_ rawValue: UInt32) { + self.rawValue = rawValue + } +} + +extension NTStatus: CustomStringConvertible { + public var description: String { + switch Status(rawValue: rawValue) { + case .success: + return "The client request is successful." + case .pending: + return "The operation that was requested is pending completion." + case .invalidSMB: + return "An invalid SMB client request is received by the server." + case .smbBadTid: + return "The client request received by the server contains an invalid TID value." + case .smbBadCommand: + return "The client request received by the server contains an unknown SMB command code." + case .smbBadUID: + return "The client request to the server contains an invalid UID value." + case .smbUseStandard: + return "The client request received by the server is for a non-standard SMB operation (for example, an SMB_COM_READ_MPX request on a non-disk share). The client SHOULD send another request with a different SMB command to perform this operation." + case .bufferOverflow: + return "The data was too large to fit into the specified buffer." + case .noMoreFiles: + return "No more files were found that match the file specification." + case .stoppedOnSymlink: + return "The create operation stopped after reaching a symbolic link." + case .notImplemented: + return "The requested operation is not implemented." + case .invalidInfoClass: + return "The specified information class is not a valid information class for the specified object." + case .invalidParameter: + return "The parameter specified in the request is not valid." + case .noSuchFile: + return "File not found." + case .noSuchDevice: + return "A device that does not exist was specified." + case .invalidDeviceRequest: + return "The specified request is not a valid operation for the target device." + case .endOfFile: + return "The end-of-file marker has been reached. There is no valid data in the file beyond this marker." + case .moreProcessingRequired: + return "If extended security has been negotiated, then this error code can be returned in the SMB_COM_SESSION_SETUP_ANDX response from the server to indicate that additional authentication information is to be exchanged. See section 2.2.4.6 for details." + case .accessDenied: + return "The client did not have the required permission needed for the operation." + case .bufferTooSmall: + return "The buffer is too small to contain the entry. No information has been written to the buffer." + case .objectNameInvalid: + return "The object name is invalid." + case .objectNameNotFound: + return "The object name is not found." + case .objectNameCollision: + return "The object name already exists." + case .sharingViolation: + return "A file cannot be opened because the share access flags are incompatible." + case .deletePending: + return "A non-close operation has been requested of a file object that has a delete pending." + case .objectPathNotFound: + return "The path to the directory specified was not found. This error is also returned on a create request if the operation requires the creation of more than one new directory level for the path specified." + case .logonFailure: + return "The attempted logon is invalid. This is either due to a bad username or authentication information." + case .badImpersonationLevel: + return "A specified impersonation level is invalid. This error is also used to indicate that a required impersonation level was not provided." + case .ioTimeout: + return "The specified I/O operation was not completed before the time-out period expired." + case .fileIsADirectory: + return "The file that was specified as a target is a directory and the caller specified that it could be anything but a directory." + case .notSupported: + return "The client request is not supported." + case .networkNameDeleted: + return "The network name specified by the client has been deleted on the server. This error is returned if the client specifies an incorrect TID or the share on the server represented by the TID was deleted." + case .badNetworkName: + return "The specified share name cannot be found on the remote server." + case .notADirectory: + return "A requested opened file is not a directory." + case .fileClosed: + return "An I/O request other than close and several other special case operations was attempted using a file object that had already been closed." + case .userSessionDeleted: + return "The user session specified by the client has been deleted on the server. This error is returned by the server if the client sends an incorrect UID." + case .connectionRefused: + return "The transport-connection attempt was refused by the remote system." + case .networkSessionExpired: + return "The client's session has expired; therefore, the client MUST re-authenticate to continue accessing remote resources." + case .smbTooManyUIDs: + return "The client has requested too many UID values from the server or the client already has an SMB session setup with this UID value." + default: + return "Unknown error: \(String(format: "0x%08X", rawValue))" + } + } +} + +extension NTStatus: CustomDebugStringConvertible { + public var debugDescription: String { + switch Status(rawValue: rawValue) { + case .success: + return "SUCCESS" + case .pending: + return "PENDING" + case .invalidSMB: + return "INVALID_SMB" + case .smbBadTid: + return "SMB_BAD_TID" + case .smbBadCommand: + return "SMB_BAD_COMMAND" + case .smbBadUID: + return "SMB_BAD_UID" + case .smbUseStandard: + return "SMB_USE_STANDARD" + case .bufferOverflow: + return "BUFFER_OVERFLOW" + case .noMoreFiles: + return "NO_MORE_FILES" + case .stoppedOnSymlink: + return "STOPPED_ON_SYMLINK" + case .notImplemented: + return "NOT_IMPLEMENTED" + case .invalidInfoClass: + return "INVALID_INFO_CLASS" + case .invalidParameter: + return "INVALID_PARAMETER" + case .noSuchDevice: + return "NO_SUCH_DEVICE" + case .noSuchFile: + return "NO_SUCH_FILE" + case .invalidDeviceRequest: + return "INVALID_DEVICE_REQUEST" + case .endOfFile: + return "END_OF_FILE" + case .moreProcessingRequired: + return "MORE_PROCESSING_REQUIRED" + case .accessDenied: + return "ACCESS_DENIED" + case .bufferTooSmall: + return "BUFFER_TOO_SMALL" + case .objectNameInvalid: + return "OBJECT_NAME_INVALID" + case .objectNameNotFound: + return "OBJECT_NAME_NOT_FOUND" + case .objectNameCollision: + return "OBJECT_NAME_COLLISION" + case .sharingViolation: + return "SHARING_VIOLATION" + case .deletePending: + return "DELETE_PENDING" + case .objectPathNotFound: + return "OBJECT_PATH_NOT_FOUND" + case .logonFailure: + return "LOGON_FAILURE" + case .badImpersonationLevel: + return "BAD_IMPERSONATION_LEVEL" + case .ioTimeout: + return "IO_TIMEOUT" + case .fileIsADirectory: + return "FILE_IS_A_DIRECTORY" + case .notSupported: + return "NOT_SUPPORTED" + case .networkNameDeleted: + return "NETWORK_NAME_DELETED" + case .badNetworkName: + return "BAD_NETWORK_NAME" + case .notADirectory: + return "NOT_A_DIRECTORY" + case .fileClosed: + return "FILE_CLOSED" + case .userSessionDeleted: + return "USER_SESSION_DELETED" + case .connectionRefused: + return "CONNECTION_REFUSED" + case .networkSessionExpired: + return "NETWORK_SESSION_EXPIRED" + case .smbTooManyUIDs: + return "SMB_TOO_MANY_UIDS" + default: + return "UNKNOWN_ERROR" + } + } +} + +public enum Status: UInt32 { + case success = 0x00000000 + case pending = 0x00000103 + case invalidSMB = 0x00010002 + case smbBadTid = 0x00050002 + case smbBadCommand = 0x00160002 + case smbBadUID = 0x005B0002 + case smbUseStandard = 0x00FB0002 + case bufferOverflow = 0x80000005 + case noMoreFiles = 0x80000006 + case stoppedOnSymlink = 0x8000002D + case notImplemented = 0xC0000002 + case invalidInfoClass = 0xC000000 + case invalidParameter = 0xC000000D + case noSuchDevice = 0xC000000E + case noSuchFile = 0xC000000F + case invalidDeviceRequest = 0xC0000010 + case endOfFile = 0xC0000011 + case moreProcessingRequired = 0xC0000016 + case accessDenied = 0xC0000022 + case bufferTooSmall = 0xC0000023 + case objectNameInvalid = 0xC0000033 + case objectNameNotFound = 0xC0000034 + case objectNameCollision = 0xC0000035 + case sharingViolation = 0xC0000043 + case deletePending = 0xC0000056 + case objectPathNotFound = 0xC000003A + case logonFailure = 0xC000006D + case badImpersonationLevel = 0xC00000A5 + case ioTimeout = 0xC00000B5 + case fileIsADirectory = 0xC00000BA + case notSupported = 0xC00000BB + case networkNameDeleted = 0xC00000C9 + case badNetworkName = 0xC00000CC + case notADirectory = 0xC0000103 + case fileClosed = 0xC0000128 + case userSessionDeleted = 0xC0000203 + case connectionRefused = 0xC0000236 + case networkSessionExpired = 0xC000035C + case smbTooManyUIDs = 0xC000205A +} + +public func ==(lhs: NTStatus, rhs: Status) -> Bool { + return lhs.rawValue == rhs.rawValue +} + +public func ==(lhs: Status, rhs: NTStatus) -> Bool { + return lhs.rawValue == rhs.rawValue +} + +public func !=(lhs: NTStatus, rhs: Status) -> Bool { + return lhs.rawValue != rhs.rawValue +} + +public func !=(lhs: Status, rhs: NTStatus) -> Bool { + return lhs.rawValue != rhs.rawValue +} + +public func ~=(pattern: Status, value: NTStatus) -> Bool { + return pattern.rawValue == value.rawValue +} diff --git a/Sources/SMBClient/Messages/Negotiate+CustomDebugStringConvertible.swift b/Sources/SMBClient/Messages/Negotiate+CustomDebugStringConvertible.swift new file mode 100644 index 0000000..1731935 --- /dev/null +++ b/Sources/SMBClient/Messages/Negotiate+CustomDebugStringConvertible.swift @@ -0,0 +1,116 @@ +import Foundation + +extension Negotiate.Request: CustomDebugStringConvertible { + public var debugDescription: String { + """ + \(header) + Negotiate Protocol Request (\(String(format: "0x%02x", header.command))) + StructureSize: \(structureSize) + DialectCount: \(dialectCount) + Security mode: \(securityMode) + Reserved: \(String(format: "%04x", reserved)) + Capabilities: \(capabilities) + Client Guid: \(clientGuid) + Client Start Time: \(clientStartTime) + \(dialects.map { " Dialect: \($0)" }.joined(separator: "\n")) + Padding: \(padding.hex) + NegotiateContextList: \(negotiateContextList.hex) + """ + } +} + +extension Negotiate.Response: CustomDebugStringConvertible { + public var debugDescription: String { + """ + \(header) + Negotiate Protocol Response (\(String(format: "0x%02x", header.command))) + StructureSize: \(structureSize) + Security mode: \(securityMode) + Dialect: \(String(format: "0x%04x", dialectRevision)) + NegotiateContextCount: \(negotiateContextCount) + Server Guid: \(serverGuid) + Capabilities: \(capabilities) + Max Transaction Size: \(maxTransactSize) + Max Read Size: \(maxReadSize) + Max Write Size: \(maxWriteSize) + Current Time: \(FileTime(systemTime)) + Boot Time: \(FileTime(serverStartTime)) + Blob Offset: \(securityBufferOffset) + Blob Length: \(securityBufferLength) + Security Blob: \(securityBuffer.hex) + NegotiateContextOffset: \(String(format: "0x%08x", negotiateContextOffset)) + """ + } +} + +extension Negotiate.SecurityMode: CustomDebugStringConvertible { + public var debugDescription: String { + var values = [String]() + if contains(.signingEnabled) { + values.append("Signing enabled") + } + if contains(.signingRequired) { + values.append("Signing required") + } + + if values.isEmpty { + return "\(String(format: "0x%04x", rawValue))" + } else { + return "\(String(format: "0x%04x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} + +extension Negotiate.Capabilities: CustomDebugStringConvertible { + public var debugDescription: String { + var values = [String]() + + if contains(.dfs) { + values.append("DFS") + } + if contains(.leasing) { + values.append("LEASING") + } + if contains(.largeMtu) { + values.append("LARGE MTU") + } + if contains(.multiChannel) { + values.append("MULTI CHANNEL") + } + if contains(.persistentHandles) { + values.append("PERSISTENT HANDLES") + } + if contains(.directoryLeasing) { + values.append("DIRECTORY LEASING") + } + if contains(.encryption) { + values.append("ENCRYPTION") + } + if contains(.notifications) { + values.append("NOTIFICATIONS") + } + + if values.isEmpty { + return "\(String(format: "0x%08x", rawValue))" + } else { + return "\(String(format: "0x%08x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} + +extension Negotiate.Dialects: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .smb202: + return "SMB 2.0.2 (\(String(format: "0x%04x", rawValue)))" + case .smb210: + return "SMB 2.1.0 (\(String(format: "0x%04x", rawValue)))" + case .smb300: + return "SMB 3.0.0 (\(String(format: "0x%04x", rawValue)))" + case .smb302: + return "SMB 3.0.2 (\(String(format: "0x%04x", rawValue)))" + case .smb311: + return "SMB 3.1.1 (\(String(format: "0x%04x", rawValue)))" + } + } +} diff --git a/Sources/SMBClient/Messages/Negotiate.swift b/Sources/SMBClient/Messages/Negotiate.swift index 8e570db..3bfa116 100644 --- a/Sources/SMBClient/Messages/Negotiate.swift +++ b/Sources/SMBClient/Messages/Negotiate.swift @@ -142,71 +142,3 @@ public enum Negotiate { case smb311 = 0x0311 } } - -extension Negotiate.Response: CustomDebugStringConvertible { - public var debugDescription: String { - """ - \(header) - Negotiate Protocol Response (\(String(format: "0x%02x", header.command))) - StructureSize: \(structureSize) - Security mode: 0x\(String(format: "%04x", securityMode.rawValue)) (\(securityMode)) - Dialect: 0x\(String(format: "%04x", dialectRevision)) - NegotiateContextCount: \(negotiateContextCount) - Server Guid: \(serverGuid) - Capabilities: 0x\(String(format: "%08x", capabilities.rawValue)) (\(capabilities)) - Max Transaction Size: \(maxTransactSize) - Max Read Size: \(maxReadSize) - Max Write Size: \(maxWriteSize) - Current Time: \(FileTime(systemTime)) - Boot Time: \(FileTime(serverStartTime)) - Blob Offset: \(securityBufferOffset) - Blob Length: \(securityBufferLength) - Security Blob: \(securityBuffer.hex) - NegotiateContextOffset: \(String(format: "0x%08x", negotiateContextOffset)) - """ - } -} - -extension Negotiate.SecurityMode: CustomDebugStringConvertible { - public var debugDescription: String { - var values = [String]() - if contains(.signingEnabled) { - values.append("signingEnabled") - } - if contains(.signingRequired) { - values.append("signingRequired") - } - return values.joined(separator: "|") - } -} - -extension Negotiate.Capabilities: CustomDebugStringConvertible { - public var debugDescription: String { - var values = [String]() - if contains(.dfs) { - values.append("dfs") - } - if contains(.leasing) { - values.append("leasing") - } - if contains(.largeMtu) { - values.append("largeMtu") - } - if contains(.multiChannel) { - values.append("multiChannel") - } - if contains(.persistentHandles) { - values.append("persistentHandles") - } - if contains(.directoryLeasing) { - values.append("directoryLeasing") - } - if contains(.encryption) { - values.append("encryption") - } - if contains(.notifications) { - values.append("notifications") - } - return values.joined(separator: "|") - } -} diff --git a/Sources/SMBClient/Messages/SessionSetup+CustomDebugStringConvertible.swift b/Sources/SMBClient/Messages/SessionSetup+CustomDebugStringConvertible.swift new file mode 100644 index 0000000..b0b8b57 --- /dev/null +++ b/Sources/SMBClient/Messages/SessionSetup+CustomDebugStringConvertible.swift @@ -0,0 +1,108 @@ +import Foundation + +extension SessionSetup.Request: CustomDebugStringConvertible { + public var debugDescription: String { + """ + \(header) + Session Setup Request (\(String(format: "0x%02x", header.command))) + StructureSize: \(structureSize) + Flags: \(flags.rawValue) + Security mode: 0x\(String(format: "%04x", securityMode.rawValue)) (\(securityMode)) + Capabilities: \(capabilities) + Channel: \(String(format: "0x%08x", channel)) + Blob Offset: \(securityBufferOffset) + Blob Length: \(securityBufferLength) + Previous Session Id: \(String(format: "0x%016llx", previousSessionId)) + Security Blob: \(securityBuffer.hex) + """ + } +} + +extension SessionSetup.Response: CustomDebugStringConvertible { + public var debugDescription: String { + """ + \(header) + Session Setup Response (\(String(format: "0x%02x", header.command))) + StructureSize: \(structureSize) + Session Flags: \(sessionFlags) + Blob Offset: \(securityBufferOffset) + Blob Length: \(securityBufferLength) + Security Blob: \(buffer.hex) + """ + } +} + +extension SessionSetup.Flags: CustomDebugStringConvertible { + public var debugDescription: String { + var values = [String]() + if contains(.binding) { + values.append("Session Binding Request") + } + + if values.isEmpty { + return "0x\(String(format: "%02x", rawValue))" + } else { + return "0x\(String(format: "%02x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} + +extension SessionSetup.SecurityMode: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .signingEnabled: + return "Signing enabled" + case .signingRequired: + return "Signing required" + default: + return "0x\(String(format: "%02x", rawValue))" + } + } +} + +extension SessionSetup.Capabilities: CustomDebugStringConvertible { + public var debugDescription: String { + var values = [String]() + + if contains(.dfs) { + values.append("DFS") + } + if contains(.unused1) { + values.append("Unused1") + } + if contains(.unused2) { + values.append("Unused2") + } + if contains(.unused3) { + values.append("Unused3") + } + + if values.isEmpty { + return "0x\(String(format: "%08x", rawValue))" + } else { + return "0x\(String(format: "%08x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} + +extension SessionSetup.SessionFlags: CustomDebugStringConvertible { + public var debugDescription: String { + var values = [String]() + + if contains(.guest) { + values.append("Guest") + } + if contains(.nullSession) { + values.append("Null") + } + if contains(.encryptData) { + values.append("Encrypt") + } + + if values.isEmpty { + return "0x\(String(format: "%04x", rawValue))" + } else { + return "0x\(String(format: "%04x", rawValue)) (\(values.joined(separator: ", ")))" + } + } +} diff --git a/Sources/SMBClient/Session.swift b/Sources/SMBClient/Session.swift index ed1c024..ee2dab8 100644 --- a/Sources/SMBClient/Session.swift +++ b/Sources/SMBClient/Session.swift @@ -50,6 +50,7 @@ public class Session { securityMode: securityMode, dialects: dialects ) + let data = try await send(request.encoded()) let response = Negotiate.Response(data: data) @@ -83,7 +84,7 @@ public class Session { ) let response = SessionSetup.Response(data: try await send(request.encoded())) - if response.header.status == NTStatus.moreProcessingRequired { + if NTStatus(response.header.status) == .moreProcessingRequired { let challengeMessage = NTLM.ChallengeMessage(data: response.buffer) let signingKey = Crypto.randomBytes(count: 16) @@ -125,9 +126,12 @@ public class Session { treeId: treeId, sessionId: sessionId ) + let data = try await send(request.encoded()) let response = Logoff.Response(data: data) + sessionId = 0 + return response } @@ -321,7 +325,7 @@ public class Session { let queryDirectoryResponse = QueryDirectory.Response(data: Data(data[createResponse.header.nextCommand...])) files.append(contentsOf: queryDirectoryResponse.files) - if createResponse.header.status != NTStatus.noMoreFiles { + if NTStatus(createResponse.header.status) != .noMoreFiles { repeat { let fileId = createResponse.fileId @@ -341,7 +345,7 @@ public class Session { let queryDirectoryResponse = QueryDirectory.Response(data: data) files.append(contentsOf: queryDirectoryResponse.files) - if queryDirectoryResponse.header.status == NTStatus.noMoreFiles { + if NTStatus(queryDirectoryResponse.header.status) == .noMoreFiles { break } } while true @@ -386,7 +390,7 @@ public class Session { _ = try await fileStat(path: path) return true } catch let error as ErrorResponse { - if error.header.status == ErrorCodes.objectNameNotFound { + if NTStatus(error.header.status) == .objectNameNotFound { return false } throw error @@ -400,7 +404,7 @@ public class Session { let stat = try await fileStat(path: path) return stat.fileAttributes.contains(.directory) } catch let error as ErrorResponse { - if error.header.status == ErrorCodes.objectNameNotFound { + if NTStatus(error.header.status) == .objectNameNotFound { return false } throw error @@ -488,7 +492,7 @@ public class Session { var response = Read.Response(data: try await send(request.encoded())) buffer.append(response.buffer) - while response.header.status != NTStatus.endOfFile { + while NTStatus(response.header.status) != .endOfFile { let request = Read.Request( creditCharge: creditSize, messageId: messageId.next(count: UInt64(creditSize)), diff --git a/Tests/SMBClientTests/SMBClientTests.swift b/Tests/SMBClientTests/SMBClientTests.swift index d335017..7a84fd7 100644 --- a/Tests/SMBClientTests/SMBClientTests.swift +++ b/Tests/SMBClientTests/SMBClientTests.swift @@ -37,7 +37,7 @@ final class SMBClientTests: XCTestCase { try await client.login(username: username, password: password) XCTFail("Login should fail") } catch let error as ErrorResponse { - XCTAssertEqual(error.header.status, NTStatus.logonFailure) + XCTAssert(NTStatus(error.header.status) == .logonFailure) } catch { XCTFail("Unexpected error: \(error)") } @@ -89,6 +89,7 @@ final class SMBClientTests: XCTestCase { let fileManager = FileManager() let root = fixtureURL.appending(component: user.sharePath) let testFiles = try fileManager.contentsOfDirectory(atPath: root.path(percentEncoded: false)) + .filter { $0 != ".DS_Store" } .sorted { $0.localizedStandardCompare($1) == .orderedAscending } for (actualFile, expectedFile) in zip(files, testFiles) { @@ -121,6 +122,7 @@ final class SMBClientTests: XCTestCase { let fileManager = FileManager() let root = fixtureURL.appending(component: "\(shareDirectory)/\(path)") let testFiles = try fileManager.contentsOfDirectory(atPath: root.path(percentEncoded: false)) + .filter { $0 != ".DS_Store" } .sorted { $0.localizedStandardCompare($1) == .orderedAscending } for (actualFile, expectedFile) in zip(files, testFiles) { @@ -163,6 +165,7 @@ final class SMBClientTests: XCTestCase { let fileManager = FileManager() let root = fixtureURL.appending(component: "\(shareDirectory)/\(path)") let testFiles = try fileManager.contentsOfDirectory(atPath: root.path(percentEncoded: false)) + .filter { $0 != ".DS_Store" } .sorted { $0.localizedStandardCompare($1) == .orderedAscending } for (actualFile, expectedFile) in zip(files, testFiles) { @@ -199,7 +202,7 @@ final class SMBClientTests: XCTestCase { try await client.connectShare("Nonexistent Share") XCTFail("Tree connect should fail") } catch let error as ErrorResponse { - XCTAssertEqual(error.header.status, ErrorCodes.badNetworkName) + XCTAssert(NTStatus(error.header.status) == .badNetworkName) } catch { XCTFail("Unexpected error: \(error)") } @@ -217,7 +220,7 @@ final class SMBClientTests: XCTestCase { _ = try await client.listDirectory(path: "Nonexistent Directory") XCTFail("List directory should fail") } catch let error as ErrorResponse { - XCTAssertEqual(error.header.status, ErrorCodes.objectNameNotFound) + XCTAssert(NTStatus(error.header.status) == .objectNameNotFound) } catch { XCTFail("Unexpected error: \(error)") }