From 93b6da4855b451790a1ea8f98187e4c7b1de8fd6 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 18 Oct 2023 14:06:10 -0500 Subject: [PATCH] Logs sideloading-related events with OSLog --- AltStore.xcodeproj/project.pbxproj | 4 ++ .../Operations/AuthenticationOperation.swift | 8 ++- .../BackgroundRefreshAppsOperation.swift | 11 ++-- AltStore/Operations/BackupAppOperation.swift | 4 +- .../Operations/DownloadAppOperation.swift | 8 +++ .../FetchProvisioningProfilesOperation.swift | 50 ++++++++++++++++--- AltStore/Operations/InstallAppOperation.swift | 42 +++++++++++++++- .../Operations/RemoveAppBackupOperation.swift | 6 ++- AltStore/Operations/RemoveAppOperation.swift | 2 + AltStore/Operations/ResignAppOperation.swift | 7 ++- AltStore/Operations/SendAppOperation.swift | 2 + AltStore/Operations/VerifyAppOperation.swift | 6 +-- AltStoreCore/Extensions/Logger+AltStore.swift | 33 ++++++++++++ 13 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 AltStoreCore/Extensions/Logger+AltStore.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index eba01447..d3f30f18 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -354,6 +354,7 @@ D51AD27E29356B7B00967AAA /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = D51AD27C29356B7B00967AAA /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; }; D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; D51AD28029356B8000967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; + D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */; }; D52C08EE28AEC37A006C4AE5 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */; }; D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; }; D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */; }; @@ -1011,6 +1012,7 @@ D5189C002A01BC6800F44625 /* UserInfoValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoValue.swift; sourceTree = ""; }; D51AD27C29356B7B00967AAA /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = ""; }; D51AD27D29356B7B00967AAA /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = ""; }; + D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+AltStore.swift"; sourceTree = ""; }; D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; D52E988928D002D30032BE6B /* AltStore 11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 11.xcdatamodel"; sourceTree = ""; }; D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailCollectionViewController.swift; sourceTree = ""; }; @@ -1723,6 +1725,7 @@ 0E05025B2BEC947000879B5C /* String+SideStore.swift */, D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */, D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */, + D52A2F962ACB40F700BDF8E3 /* Logger+AltStore.swift */, ); path = Extensions; sourceTree = ""; @@ -3031,6 +3034,7 @@ BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */, BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */, D519AD46292D665B004B12F9 /* Managed.swift in Sources */, + D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */, BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */, D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */, 0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */, diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index 40ef1e1d..901adffd 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -205,7 +205,11 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A { guard !self.isFinished else { return } - print("Finished authenticating with result:", result.error?.localizedDescription ?? "success") + switch result + { + case .failure(let error): Logger.sideload.error("Failed to authenticate account. \(error.localizedDescription, privacy: .public)") + case .success((let team, _, _)): Logger.sideload.notice("Authenticated account for team \(team.identifier, privacy: .public).") + } let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() context.perform { @@ -348,6 +352,8 @@ private extension AuthenticationOperation if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword { + Logger.sideload.notice("Authenticating Apple ID...") + self.authenticate(appleID: appleID, password: password) { (result) in switch result { diff --git a/AltStore/Operations/BackgroundRefreshAppsOperation.swift b/AltStore/Operations/BackgroundRefreshAppsOperation.swift index 4db66a87..4a3f38a6 100644 --- a/AltStore/Operations/BackgroundRefreshAppsOperation.swift +++ b/AltStore/Operations/BackgroundRefreshAppsOperation.swift @@ -114,7 +114,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result // Failed too quickly for human to respond to alert, possibly still finalizing installation. // Try again in a couple seconds. - print("Failed too quickly, retrying after a few seconds...") - + Logger.sideload.error("Failed to open app too quickly, retrying after a few seconds...") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { UIApplication.shared.open(openURL, options: [:]) { (success) in if success diff --git a/AltStore/Operations/DownloadAppOperation.swift b/AltStore/Operations/DownloadAppOperation.swift index 5fce379e..040e4def 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -51,6 +51,10 @@ final class DownloadAppOperation: ResultOperation print("Downloading App:", self.bundleIdentifier) + + Logger.sideload.notice("Downloading app \(self.bundleIdentifier, privacy: .public)...") + + // Set _after_ checking self.context.error to prevent overwriting localized failure for previous errors. self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName) guard let storeApp = self.app as? StoreApp else { @@ -100,6 +104,7 @@ final class DownloadAppOperation: ResultOperation try FileManager.default.removeItem(at: self.temporaryDirectory) } catch { print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error) + Logger.sideload.error("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory, privacy: .public). \(error.localizedDescription, privacy: .public)") } super.finish(result) } @@ -156,6 +161,9 @@ private extension DownloadAppOperation { try FileManager.default.copyItem(at: application.fileURL, to: self.destinationURL, shouldReplace: true) guard let copiedApplication = ALTApplication(fileURL: self.destinationURL) else { throw OperationError.invalidApp } + + Logger.sideload.notice("Downloaded app \(copiedApplication.bundleIdentifier, privacy: .public) from \(sourceURL, privacy: .public)") + self.finish(.success(copiedApplication)) self.progress.completedUnitCount += 1 diff --git a/AltStore/Operations/FetchProvisioningProfilesOperation.swift b/AltStore/Operations/FetchProvisioningProfilesOperation.swift index 58bc5a28..8e1cb112 100644 --- a/AltStore/Operations/FetchProvisioningProfilesOperation.swift +++ b/AltStore/Operations/FetchProvisioningProfilesOperation.swift @@ -47,7 +47,9 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv return self.finish(.failure(OperationError.invalidParameters("FetchProvisioningProfilesOperation.main: self.context.team or self.context.session is nil"))) } guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) } - + + Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...") + self.progress.totalUnitCount = Int64(1 + app.appExtensions.count) self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in @@ -246,6 +248,8 @@ extension FetchProvisioningProfilesOperation if let appID = appIDs.first(where: { $0.bundleIdentifier.lowercased() == bundleIdentifier.lowercased() }) { + Logger.sideload.notice("Using existing App ID \(appID.bundleIdentifier, privacy: .public)") + completionHandler(.success(appID)) } else @@ -285,6 +289,9 @@ extension FetchProvisioningProfilesOperation do { let appID = try Result(appID, error).get() + + Logger.sideload.notice("Registered new App ID \(appID.bundleIdentifier, privacy: .public)") + completionHandler(.success(appID)) } catch ALTAppleAPIError.maximumAppIDLimitReached @@ -383,8 +390,15 @@ extension FetchProvisioningProfilesOperation let appID = appID.copy() as! ALTAppID appID.features = features - ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in - completionHandler(Result(appID, error)) + ALTAppleAPI.shared.update(appID, team: team, session: session) { (updatedAppID, error) in + let result = Result(updatedAppID, error) + switch result + { + case .success(let appID): Logger.sideload.notice("Updated features for App ID \(appID.bundleIdentifier, privacy: .public).") + case .failure(let error): Logger.sideload.error("Failed to update features for App ID \(appID.bundleIdentifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + } + + completionHandler(result) } } else @@ -402,6 +416,7 @@ extension FetchProvisioningProfilesOperation } guard var applicationGroups = entitlements[.appGroups] as? [String], !applicationGroups.isEmpty else { + Logger.sideload.notice("App ID \(appID.bundleIdentifier, privacy: .public) has no app groups, skipping assignment.") // Assigning an App ID to an empty app group array fails, // so just do nothing if there are no app groups. return completionHandler(.success(appID)) @@ -459,7 +474,10 @@ extension FetchProvisioningProfilesOperation ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in switch Result(groups, error) { - case .failure(let error): finish(.failure(error)) + case .failure(let error): + Logger.sideload.error("Failed to fetch app groups for team \(team.identifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + finish(.failure(error)) + case .success(let fetchedGroups): let dispatchGroup = DispatchGroup() @@ -484,8 +502,13 @@ extension FetchProvisioningProfilesOperation ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in switch Result(group, error) { - case .success(let group): groups.append(group) - case .failure(let error): errors.append(error) + case .success(let group): + Logger.sideload.notice("Created new App Group \(group.groupIdentifier, privacy: .public).") + groups.append(group) + + case .failure(let error): + Logger.sideload.notice("Failed to create new App Group \(adjustedGroupIdentifier, privacy: .public). \(error.localizedDescription, privacy: .public)") + errors.append(error) } dispatchGroup.leave() @@ -501,8 +524,17 @@ extension FetchProvisioningProfilesOperation else { ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in - let result = Result(success, error) - finish(result.map { _ in appID }) + let groupIDs = groups.map { $0.groupIdentifier } + switch Result(success, error) + { + case .success: + Logger.sideload.notice("Assigned App ID \(appID.bundleIdentifier, privacy: .public) to App Groups \(groupIDs.description, privacy: .public).") + finish(.success(appID)) + + case .failure(let error): + Logger.sideload.error("Failed to assign App ID \(appID.bundleIdentifier, privacy: .public) to App Groups \(groupIDs.description, privacy: .public). \(error.localizedDescription, privacy: .public)") + finish(.failure(error)) + } } } } @@ -529,6 +561,8 @@ extension FetchProvisioningProfilesOperation completionHandler(.success(profile)) case .success: + Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") + // Fetch new provisioning profile ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in completionHandler(Result(profile, error)) diff --git a/AltStore/Operations/InstallAppOperation.swift b/AltStore/Operations/InstallAppOperation.swift index d2832847..1115ca0c 100644 --- a/AltStore/Operations/InstallAppOperation.swift +++ b/AltStore/Operations/InstallAppOperation.swift @@ -47,6 +47,8 @@ final class InstallAppOperation: ResultOperation return self.finish(.failure(OperationError.invalidParameters("InstallAppOperation.main: self.context.certificate or self.context.resignedApp or self.context.provisioningProfiles is nil"))) } + Logger.sideload.notice("Installing resigned app \(resignedApp.bundleIdentifier, privacy: .public)...") + @Managed var appVersion = self.context.appVersion let storeBuildVersion = $appVersion.buildVersion @@ -255,6 +257,7 @@ final class InstallAppOperation: ResultOperation catch { print("Failed to remove refreshed .ipa: \(error)") + Logger.sideload.error("Failed to remove refreshed .ipa: \(error.localizedDescription, privacy: .public)") } } @@ -264,6 +267,43 @@ final class InstallAppOperation: ResultOperation private extension InstallAppOperation { + func receive(from connection: ServerConnection, completionHandler: @escaping (Result) -> Void) + { + connection.receiveResponse() { (result) in + do + { + let response = try result.get() + + switch response + { + case .installationProgress(let response): + Logger.sideload.debug("Installing \(self.context.resignedApp?.bundleIdentifier ?? self.context.bundleIdentifier, privacy: .public)... \((response.progress * 100).rounded())%") + + if response.progress == 1.0 + { + self.progress.completedUnitCount = self.progress.totalUnitCount + completionHandler(.success(())) + } + else + { + self.progress.completedUnitCount = Int64(response.progress * 100) + self.receive(from: connection, completionHandler: completionHandler) + } + + case .error(let response): + completionHandler(.failure(response.error)) + + default: + completionHandler(.failure(ALTServerError(.unknownRequest))) + } + } + catch + { + completionHandler(.failure(ALTServerError(error))) + } + } + } + func cleanUp() { guard !self.didCleanUp else { return } @@ -275,7 +315,7 @@ private extension InstallAppOperation } catch { - print("Failed to remove temporary directory.", error) + Logger.sideload.error("Failed to remove temporary directory: \(error.localizedDescription, privacy: .public)") } } } diff --git a/AltStore/Operations/RemoveAppBackupOperation.swift b/AltStore/Operations/RemoveAppBackupOperation.swift index 9b003e06..45827c01 100644 --- a/AltStore/Operations/RemoveAppBackupOperation.swift +++ b/AltStore/Operations/RemoveAppBackupOperation.swift @@ -8,6 +8,8 @@ import Foundation +import AltStoreCore + @objc(RemoveAppBackupOperation) final class RemoveAppBackupOperation: ResultOperation { @@ -63,14 +65,14 @@ final class RemoveAppBackupOperation: ResultOperation #else - print("Failed to remove app backup directory:", error) + Logger.sideload.error("Failed to remove app backup directory \(backupDirectoryURL.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) #endif } catch { - print("Failed to remove app backup directory:", error) + Logger.sideload.error("Failed to remove app backup directory \(backupDirectoryURL.lastPathComponent, privacy: .public). \(error.localizedDescription, privacy: .public)") self.finish(.failure(error)) } } diff --git a/AltStore/Operations/RemoveAppOperation.swift b/AltStore/Operations/RemoveAppOperation.swift index 9b5320bc..7a885bd8 100644 --- a/AltStore/Operations/RemoveAppOperation.swift +++ b/AltStore/Operations/RemoveAppOperation.swift @@ -37,6 +37,8 @@ final class RemoveAppOperation: ResultOperation return self.finish(.failure(OperationError.invalidParameters("RemoveAppOperation.main: self.context.installedApp is nil"))) } + Logger.sideload.notice("Removing app \(self.context.bundleIdentifier, privacy: .public)...") + installedApp.managedObjectContext?.perform { let resignedBundleIdentifier = installedApp.resignedBundleIdentifier diff --git a/AltStore/Operations/ResignAppOperation.swift b/AltStore/Operations/ResignAppOperation.swift index d697533d..e4b899ad 100644 --- a/AltStore/Operations/ResignAppOperation.swift +++ b/AltStore/Operations/ResignAppOperation.swift @@ -49,6 +49,8 @@ final class ResignAppOperation: ResultOperation "self.context.certificate is nil"))) } + Logger.sideload.notice("Resigning app \(self.context.bundleIdentifier, privacy: .public)...") + // Prepare app bundle let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2) self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3) @@ -56,8 +58,6 @@ final class ResignAppOperation: ResultOperation let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in guard let appBundleURL = self.process(result) else { return } - print("Resigning App:", self.context.bundleIdentifier) - // Resign app bundle let resignProgress = self.resignAppBundle(at: appBundleURL, team: team, certificate: certificate, profiles: Array(profiles.values)) { (result) in guard let resignedURL = self.process(result) else { return } @@ -71,6 +71,9 @@ final class ResignAppOperation: ResultOperation // Use appBundleURL since we need an app bundle, not .ipa. guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp } + + Logger.sideload.notice("Resigned app \(self.context.bundleIdentifier, privacy: .public) to \(resignedApplication.bundleIdentifier, privacy: .public).") + self.finish(.success(resignedApplication)) } catch diff --git a/AltStore/Operations/SendAppOperation.swift b/AltStore/Operations/SendAppOperation.swift index 99f2b3e0..bf5ba7e8 100644 --- a/AltStore/Operations/SendAppOperation.swift +++ b/AltStore/Operations/SendAppOperation.swift @@ -40,6 +40,8 @@ final class SendAppOperation: ResultOperation<()> return self.finish(.failure(OperationError.invalidParameters("SendAppOperation.main: self.resignedApp is nil"))) } + Logger.sideload.notice("Sending app \(self.context.bundleIdentifier, privacy: .public) to AltServer \(server.localizedName ?? "nil", privacy: .public)...") + // self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa. let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL) let fileURL = InstalledApp.refreshedIPAURL(for: app) diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index 12d498f4..7f857179 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -233,9 +233,9 @@ private extension VerifyAppOperation let data = try Data(contentsOf: ipaURL) let sha256Hash = SHA256.hash(data: data) let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined() - - print("[ALTLog] Comparing app hash (\(hashString)) against expected hash (\(expectedHash))...") - + + Logger.sideload.debug("Comparing app hash (\(hashString, privacy: .public)) against expected hash (\(expectedHash, privacy: .public))...") + guard hashString == expectedHash else { throw VerificationError.mismatchedHash(hashString, expectedHash: expectedHash, app: app) } } diff --git a/AltStoreCore/Extensions/Logger+AltStore.swift b/AltStoreCore/Extensions/Logger+AltStore.swift new file mode 100644 index 00000000..41d604ad --- /dev/null +++ b/AltStoreCore/Extensions/Logger+AltStore.swift @@ -0,0 +1,33 @@ +// +// Logger+AltStore.swift +// AltStoreCore +// +// Created by Riley Testut on 10/2/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +@_exported import OSLog + +public extension Logger +{ + static let altstoreSubsystem = Bundle.main.bundleIdentifier! + + static let sideload = Logger(subsystem: altstoreSubsystem, category: "Sideload") +} + +@available(iOS 15, *) +public extension OSLogEntryLog.Level +{ + var localizedName: String { + switch self + { + case .undefined: return NSLocalizedString("Undefined", comment: "") + case .debug: return NSLocalizedString("Debug", comment: "") + case .info: return NSLocalizedString("Info", comment: "") + case .notice: return NSLocalizedString("Notice", comment: "") + case .error: return NSLocalizedString("Error", comment: "") + case .fault: return NSLocalizedString("Fault", comment: "") + @unknown default: return NSLocalizedString("Unknown", comment: "") + } + } +}