diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index ddfa9d9b5e..fe24793299 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */; }; D03120FE1DA562E9006A2A60 /* CachedPeerDataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */; }; D03121001DA579A0006A2A60 /* PeerNotificationSettingsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */; }; + D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */; }; + D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */; }; D033A6F71C73D512006A2EAB /* MessageHistoryUnsentTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */; }; D033A6F91C73E440006A2EAB /* UnsentMessageHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */; }; D03BCCF81C73561C0097A291 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCF71C73561C0097A291 /* Table.swift */; }; @@ -182,8 +184,6 @@ D0D949F51D35353900740E02 /* MappedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D949F41D35353900740E02 /* MappedFile.swift */; }; D0DA44411E4C7868005FDCA7 /* IncrementalUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44401E4C7868005FDCA7 /* IncrementalUpgrade.swift */; }; D0DA44421E4C7868005FDCA7 /* IncrementalUpgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44401E4C7868005FDCA7 /* IncrementalUpgrade.swift */; }; - D0DA44451E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44441E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift */; }; - D0DA44461E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44441E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift */; }; D0DA44481E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */; }; D0DA44491E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */; }; D0DF0C8F1D81A350008AEB01 /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C8E1D81A350008AEB01 /* PeerView.swift */; }; @@ -286,6 +286,7 @@ D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettings.swift; sourceTree = ""; }; D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerDataTable.swift; sourceTree = ""; }; D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsTable.swift; sourceTree = ""; }; + D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteInterface.swift; sourceTree = ""; }; D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryUnsentTable.swift; sourceTree = ""; }; D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsentMessageHistoryView.swift; sourceTree = ""; }; D03BCCF71C73561C0097A291 /* Table.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = ""; }; @@ -365,7 +366,6 @@ D0D949F21D35302600740E02 /* RandomAccessResourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RandomAccessResourceTests.swift; sourceTree = ""; }; D0D949F41D35353900740E02 /* MappedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedFile.swift; sourceTree = ""; }; D0DA44401E4C7868005FDCA7 /* IncrementalUpgrade.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncrementalUpgrade.swift; sourceTree = ""; }; - D0DA44441E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncrementalUpgrade_v11_v12.swift; sourceTree = ""; }; D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxAccess.swift; sourceTree = ""; }; D0DF0C8E1D81A350008AEB01 /* PeerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = ""; }; D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewTracker.swift; sourceTree = ""; }; @@ -501,20 +501,11 @@ D0DA443F1E4C7834005FDCA7 /* Upgrade */ = { isa = PBXGroup; children = ( - D0DA44431E4C796C005FDCA7 /* v11-v12 */, D0DA44401E4C7868005FDCA7 /* IncrementalUpgrade.swift */, ); name = Upgrade; sourceTree = ""; }; - D0DA44431E4C796C005FDCA7 /* v11-v12 */ = { - isa = PBXGroup; - children = ( - D0DA44441E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift */, - ); - name = "v11-v12"; - sourceTree = ""; - }; D0E1DE161C5EB06000C7826E /* Tables */ = { isa = PBXGroup; children = ( @@ -597,6 +588,7 @@ D0D949F41D35353900740E02 /* MappedFile.swift */, D0D510FF1D64A58900A97B8A /* IpcPipe.swift */, D07827C01E0079CB00071108 /* StringIndexTokens.swift */, + D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */, ); name = Utils; sourceTree = ""; @@ -883,6 +875,7 @@ buildActionMask = 2147483647; files = ( D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */, + D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */, D050F2671E4A5B5A00988324 /* TimestampBasedMessageAttributesTable.swift in Sources */, D050F2681E4A5B5A00988324 /* TimestampBasedMessageAttributesIndexTable.swift in Sources */, D0DA44421E4C7868005FDCA7 /* IncrementalUpgrade.swift in Sources */, @@ -956,7 +949,6 @@ D0F7B1DE1E045C6A007EB8A5 /* PeerChatTopIndexableMessageIds.swift in Sources */, D0B4184A1D7DFE20004562A4 /* UnsentMessageHistoryView.swift in Sources */, D0B418231D7DFE0C004562A4 /* SimpleDictionary.swift in Sources */, - D0DA44461E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift in Sources */, D0B4185C1D7DFE2F004562A4 /* MurMurHash32.m in Sources */, D0F7B1E01E045C6A007EB8A5 /* ItemCacheTable.swift in Sources */, D0F7B1E11E045C6A007EB8A5 /* ReverseIndexReferenceTable.swift in Sources */, @@ -999,6 +991,7 @@ buildActionMask = 2147483647; files = ( D08775061E3E3F2100A97350 /* PreferencesView.swift in Sources */, + D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */, D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */, D0F3CC741DDE1EB9008148FA /* ItemCacheMetaTable.swift in Sources */, D0DA44411E4C7868005FDCA7 /* IncrementalUpgrade.swift in Sources */, @@ -1073,7 +1066,6 @@ D033A6F71C73D512006A2EAB /* MessageHistoryUnsentTable.swift in Sources */, D00C7CD41E365C4E0080C3D5 /* PeerChatListInclusion.swift in Sources */, D0A18D671E16874D004C6734 /* UnreadMessageCountsView.swift in Sources */, - D0DA44451E4C798E005FDCA7 /* IncrementalUpgrade_v11_v12.swift in Sources */, D0D511021D64D73D00A97B8A /* IpcNotifier.mm in Sources */, D0B844511DAC04FE005F29E1 /* PeerPresence.swift in Sources */, D07827C11E0079CB00071108 /* StringIndexTokens.swift in Sources */, diff --git a/Postbox/IncrementalUpgrade_v11_v12.swift b/Postbox/IncrementalUpgrade_v11_v12.swift deleted file mode 100644 index f822c0461b..0000000000 --- a/Postbox/IncrementalUpgrade_v11_v12.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -#if os(macOS) - import SwiftSignalKitMac -#else - import SwiftSignalKit -#endif - -final class IncrementalUpgrade_v11_v12: IncrementalUpgrade { - func upgrade(from previous: ValueBox, tmpBasePath: String) -> Signal { - assertionFailure() - - return .never() - } -} diff --git a/Postbox/MetadataTable.swift b/Postbox/MetadataTable.swift index f42089b735..4d2070e844 100644 --- a/Postbox/MetadataTable.swift +++ b/Postbox/MetadataTable.swift @@ -5,6 +5,40 @@ private enum MetadataKey: Int32 { case State = 2 case TransactionStateVersion = 3 case MasterClientId = 4 + case AccessChallenge = 5 +} + +enum PostboxAccessChallengeData: Coding { + case none + case numericalPassword(String) + case plaintextPassword(String) + + init(decoder: Decoder) { + switch decoder.decodeInt32ForKey("r") as Int32 { + case 0: + self = .none + case 1: + self = .numericalPassword(decoder.decodeStringForKey("t")) + case 2: + self = .plaintextPassword(decoder.decodeStringForKey("t")) + default: + assertionFailure() + self = .none + } + } + + func encode(_ encoder: Encoder) { + switch self { + case .none: + encoder.encodeInt32(0, forKey: "r") + case let .numericalPassword(text): + encoder.encodeInt32(1, forKey: "r") + encoder.encodeString(text, forKey: "t") + case let .plaintextPassword(text): + encoder.encodeInt32(2, forKey: "r") + encoder.encodeString(text, forKey: "t") + } + } } final class MetadataTable: Table { @@ -102,6 +136,20 @@ final class MetadataTable: Table { self.valueBox.set(self.table, key: self.key(.MasterClientId), value: buffer) } + func accessChallengeData() -> PostboxAccessChallengeData { + if let value = self.valueBox.get(self.table, key: self.key(.AccessChallenge)) { + return PostboxAccessChallengeData(decoder: Decoder(buffer: value)) + } else { + return .none + } + } + + func setAccessChallengeData(_ data: PostboxAccessChallengeData) { + let encoder = Encoder() + data.encode(encoder) + self.valueBox.set(self.table, key: self.key(.AccessChallenge), value: encoder.readBufferNoCopy()) + } + override func clearMemoryCache() { self.cachedState = nil } diff --git a/Postbox/PostboxAccess.swift b/Postbox/PostboxAccess.swift index 36690f3485..fa266d7102 100644 --- a/Postbox/PostboxAccess.swift +++ b/Postbox/PostboxAccess.swift @@ -7,16 +7,56 @@ import Foundation public enum PostboxAuthorizationChallenge { case numericPassword(length: Int32) - case arbitraryPassword() + case arbitraryPassword } public enum PostboxAccess { - case unlocked(Postbox) + case unlocked case locked(PostboxAuthorizationChallenge) } +private final class PostboxAccessHelper { + let queue: Queue + let valueBox: ValueBox + let metadataTable: MetadataTable + + init(queue: Queue, basePath: String) { + self.queue = queue + self.valueBox = SqliteValueBox(basePath: basePath + "/db", queue: self.queue) + self.metadataTable = MetadataTable(valueBox: self.valueBox, table: MetadataTable.tableSpec(0)) + } +} + public func accessPostbox(basePath: String, password: String?) -> Signal { return Signal { subscriber in + let queue = Queue() + + queue.async { + let postbox = PostboxAccessHelper(queue: queue, basePath: basePath) + let challengeData = postbox.metadataTable.accessChallengeData() + switch challengeData { + case .none: + subscriber.putNext(.unlocked) + subscriber.putCompletion() + case let .numericalPassword(text): + if text == password { + subscriber.putNext(.unlocked) + subscriber.putCompletion() + } else { + subscriber.putNext(.locked(.numericPassword(length: Int32(text.characters.count)))) + subscriber.putCompletion() + } + case let .plaintextPassword(text): + if text == password { + subscriber.putNext(.unlocked) + subscriber.putCompletion() + } else { + subscriber.putNext(.locked(.arbitraryPassword)) + subscriber.putCompletion() + } + } + } + return ActionDisposable { } } diff --git a/Postbox/SqliteInterface.swift b/Postbox/SqliteInterface.swift new file mode 100644 index 0000000000..661c8017f5 --- /dev/null +++ b/Postbox/SqliteInterface.swift @@ -0,0 +1,116 @@ +import Foundation +import sqlcipher + +private final class SqliteInterfaceStatement { + let statement: OpaquePointer? + + init(statement: OpaquePointer?) { + self.statement = statement + } + + func bind(_ index: Int, data: UnsafeRawPointer, length: Int) { + sqlite3_bind_blob(statement, Int32(index), data, Int32(length), nil) + } + + func bind(_ index: Int, number: Int64) { + sqlite3_bind_int64(statement, Int32(index), number) + } + + func bindNull(_ index: Int) { + sqlite3_bind_null(statement, Int32(index)) + } + + func bind(_ index: Int, number: Int32) { + sqlite3_bind_int(statement, Int32(index), number) + } + + func reset() { + sqlite3_reset(statement) + sqlite3_clear_bindings(statement) + } + + func step() -> Bool { + let result = sqlite3_step(statement) + if result != SQLITE_ROW && result != SQLITE_DONE { + assertionFailure("Sqlite error \(result)") + } + return result == SQLITE_ROW + } + + func int32At(_ index: Int) -> Int32 { + return sqlite3_column_int(statement, Int32(index)) + } + + func int64At(_ index: Int) -> Int64 { + return sqlite3_column_int64(statement, Int32(index)) + } + + func valueAt(_ index: Int) -> ReadBuffer { + let valueLength = sqlite3_column_bytes(statement, Int32(index)) + let valueData = sqlite3_column_blob(statement, Int32(index)) + + let valueMemory = malloc(Int(valueLength))! + memcpy(valueMemory, valueData, Int(valueLength)) + return ReadBuffer(memory: valueMemory, length: Int(valueLength), freeWhenDone: true) + } + + func keyAt(_ index: Int) -> ValueBoxKey { + let valueLength = sqlite3_column_bytes(statement, Int32(index)) + let valueData = sqlite3_column_blob(statement, Int32(index)) + + let key = ValueBoxKey(length: Int(valueLength)) + memcpy(key.memory, valueData, Int(valueLength)) + return key + } + + func destroy() { + sqlite3_finalize(statement) + } +} + +public final class SqliteStatementCursor { + private let statement: SqliteInterfaceStatement + + fileprivate init(statement: SqliteInterfaceStatement) { + self.statement = statement + } + + public func getInt32(at index: Int) -> Int32 { + return self.statement.int32At(index) + } + + public func getInt64(at index: Int) -> Int64 { + return self.statement.int64At(index) + } + + public func getString(at index: Int) -> String { + let value = self.statement.valueAt(index) + if let string = String(data: value.makeData(), encoding: .utf8) { + return string + } else { + return "" + } + } +} + +public final class SqliteInterface { + private let database: Database + + init(databasePath: String) { + self.database = Database(databasePath) + } + + func select(_ query: String, _ f: (SqliteStatementCursor) -> Bool) { + var statement: OpaquePointer? = nil + sqlite3_prepare_v2(database.handle, query, -1, &statement, nil) + let preparedStatement = SqliteInterfaceStatement(statement: statement) + let cursor = SqliteStatementCursor(statement: preparedStatement) + while preparedStatement.step() { + if !f(cursor) { + break + } + } + preparedStatement.reset() + preparedStatement.destroy() + } +}