Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backup Passcode Notification #61

Merged
merged 1 commit into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions Netimobiledevice/Backup/Mobilebackup2Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public sealed class Mobilebackup2Service : BaseService

private const string SERVICE_NAME = "com.apple.mobilebackup2";

private CancellationTokenSource _internalCts = new CancellationTokenSource();
private bool _passcodeRequired;

/// <summary>
/// iTunes files to be inserted into the Info.plist file.
/// </summary>
Expand Down Expand Up @@ -357,10 +360,12 @@ private static async Task VersionExchange(DeviceLinkService dl, CancellationToke
/// <returns></returns>
public async Task<ResultCode> Backup(bool fullBackup = true, string backupDirectory = ".", CancellationToken cancellationToken = default)
{
_internalCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

string deviceDirectory = Path.Combine(backupDirectory, Lockdown.Udid);
Directory.CreateDirectory(deviceDirectory);

using (DeviceLinkService dl = await GetDeviceLink(backupDirectory, cancellationToken).ConfigureAwait(false)) {
using (DeviceLinkService dl = await GetDeviceLink(backupDirectory, _internalCts.Token).ConfigureAwait(false)) {
dl.BeforeReceivingFile += DeviceLink_BeforeReceivingFile;
dl.Completed += DeviceLink_Completed;
dl.Error += DeviceLink_Error;
Expand All @@ -373,16 +378,21 @@ public async Task<ResultCode> Backup(bool fullBackup = true, string backupDirect
dl.Started += DeviceLink_Started;

using (NotificationProxyService np = new NotificationProxyService(this.Lockdown)) {
np.ReceivedNotification += NotificationProxy_ReceivedNotification;
np.ObserveNotification(ReceivableNotification.SyncCancelRequest);
np.ObserveNotification(ReceivableNotification.LocalAuthenticationUiPresented);
np.ObserveNotification(ReceivableNotification.LocalAuthenticationUiDismissed);

using (AfcService afc = new AfcService(this.Lockdown)) {
using (BackupLock backupLock = new BackupLock(afc, np)) {
await backupLock.AquireBackupLock(cancellationToken).ConfigureAwait(false);
await backupLock.AquireBackupLock(_internalCts.Token).ConfigureAwait(false);

// Create Info.plist
string infoPlistPath = Path.Combine(deviceDirectory, "Info.plist");
DictionaryNode infoPlist = await CreateInfoPlist(afc, cancellationToken).ConfigureAwait(false);
DictionaryNode infoPlist = await CreateInfoPlist(afc, _internalCts.Token).ConfigureAwait(false);
using (FileStream fs = File.OpenWrite(infoPlistPath)) {
byte[] infoPlistData = PropertyList.SaveAsByteArray(infoPlist, PlistFormat.Xml);
await fs.WriteAsync(infoPlistData, cancellationToken).ConfigureAwait(false);
await fs.WriteAsync(infoPlistData, _internalCts.Token).ConfigureAwait(false);
}

// Create Status.plist file if doesn't exist.
Expand All @@ -397,7 +407,7 @@ public async Task<ResultCode> Backup(bool fullBackup = true, string backupDirect
{ "SnapshotState", new StringNode(nameof(SnapshotState.Finished).ToLowerInvariant()) },
{ "UUID", new StringNode(Guid.NewGuid().ToString()) }
};
await File.WriteAllBytesAsync(statusPlistPath, PropertyList.SaveAsByteArray(statusPlist, PlistFormat.Binary), cancellationToken).ConfigureAwait(false);
await File.WriteAllBytesAsync(statusPlistPath, PropertyList.SaveAsByteArray(statusPlist, PlistFormat.Binary), _internalCts.Token).ConfigureAwait(false);
}

// Create Manifest.plist if doesn't exist.
Expand All @@ -413,13 +423,39 @@ public async Task<ResultCode> Backup(bool fullBackup = true, string backupDirect
};
dl.SendProcessMessage(message);

return await dl.DlLoop(cancellationToken).ConfigureAwait(false);
// Wait for 3 seconds to see if the device passcode is requested
await Task.Delay(3000, _internalCts.Token).ConfigureAwait(false);
while (_passcodeRequired) {
// Keep waiting till the passcode has been entered
await Task.Delay(3000, _internalCts.Token).ConfigureAwait(false);
}

return await dl.DlLoop(_internalCts.Token).ConfigureAwait(false);
}
}
}
}
}

private void NotificationProxy_ReceivedNotification(object? sender, ReceivedNotificationEventArgs e)
{
if (e.Event == ReceivableNotification.LocalAuthenticationUiPresented) {
// iOS versions 15.7.1 and anything 16.1 or newer will require you to input a passcode before
// it can start a backup so we make sure to notify the user about this.
if ((Lockdown.OsVersion >= new Version(15, 7, 1) && Lockdown.OsVersion < new Version(16, 0)) ||
Lockdown.OsVersion >= new Version(16, 1)) {
_passcodeRequired = true;
PasscodeRequiredForBackup?.Invoke(this, EventArgs.Empty);
}
}
else if (e.Event == ReceivableNotification.LocalAuthenticationUiDismissed) {
_passcodeRequired = false;
}
else if (e.Event == ReceivableNotification.SyncCancelRequest) {
_internalCts.Cancel();
}
}

/// <summary>
/// Restore a pre existing backup to the connected device.
/// </summary>
Expand Down
13 changes: 8 additions & 5 deletions Netimobiledevice/NotificationProxy/NotificationProxyService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Netimobiledevice.Exceptions;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Lockdown.Services;
using Netimobiledevice.Plist;
Expand Down Expand Up @@ -44,7 +45,9 @@ public sealed class NotificationProxyService : BaseService
{ ReceivableNotification.ItdbprepDidEnd, "com.apple.itdbprep.notification.didEnd" },
{ ReceivableNotification.LanguageChanged, "com.apple.language.changed" },
{ ReceivableNotification.AddressBookPreferenceChanged, "com.apple.AddressBook.PreferenceChanged" },
{ ReceivableNotification.RequestPair, "com.apple.mobile.lockdown.request_pair" }
{ ReceivableNotification.RequestPair, "com.apple.mobile.lockdown.request_pair" },
{ ReceivableNotification.LocalAuthenticationUiPresented , "com.apple.LocalAuthentication.ui.presented" },
{ ReceivableNotification.LocalAuthenticationUiDismissed, "com.apple.LocalAuthentication.ui.dismissed" }
};
/// <summary>
/// Host-To-Device notifications.
Expand Down Expand Up @@ -89,16 +92,16 @@ public override void Dispose()
if (dict.ContainsKey("Command") && dict["Command"].AsStringNode().Value == "RelayNotification") {
if (dict.ContainsKey("Name")) {
string notificationName = dict["Name"].AsStringNode().Value;
Lockdown.Logger.LogDebug($"Got notification {notificationName}");
Lockdown.Logger.LogDebug("Got notification {notificationName}", notificationName);
return notificationName;
}
}
else if (dict.ContainsKey("Command") && dict["Command"].AsStringNode().Value == "ProxyDeath") {
Lockdown.Logger.LogError("NotificationProxy died");
throw new Exception("Notification proxy died, can't listen to notifications anymore");
throw new NetimobiledeviceException("Notification proxy died, can't listen to notifications anymore");
}
else if (dict.ContainsKey("Command")) {
Lockdown.Logger.LogWarning($"Unknown NotificationProxy command {dict["Command"]}");
Lockdown.Logger.LogWarning("Unknown NotificationProxy command {command}", dict["Command"]);
}
}
}
Expand Down Expand Up @@ -148,7 +151,7 @@ private async void NotificationListener_DoWork(object? sender, DoWorkEventArgs e
}
catch (Exception ex) {
if (!notificationListener.CancellationPending) {
Lockdown.Logger.LogError($"Notification proxy listener has an error: {ex}");
Lockdown.Logger.LogError(ex, "Notification proxy listener has an error");
throw;
}
}
Expand Down
4 changes: 3 additions & 1 deletion Netimobiledevice/NotificationProxy/ReceivableNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public enum ReceivableNotification
ItdbprepDidEnd,
LanguageChanged,
AddressBookPreferenceChanged,
RequestPair
RequestPair,
LocalAuthenticationUiPresented,
LocalAuthenticationUiDismissed
}
}
Loading