ish/app/AppDelegate.m
Saagar Jha 265a4ff08a Promote hostname override to a friendly preference
Users really hate that we put "localhost" in their prompt as a result of
iOS 16 anti-fingerprinting obfuscating the device name. We already have
a mechanism for overriding this that we use for App Store screenshots,
so we might as well make it official and expose it for those that care.

The logic for what the actual uname hostname is as follows:

* If the user sets the preference, use that.
* On iOS <16, if the preference is unset, use what real uname returns.
* On iOS 16+, use UIDevice.name. This at least changes "localhost" to
something like "iPad".
2024-09-03 21:21:57 -07:00

363 lines
14 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AppDelegate.m
// iSH
//
// Created by Theodore Dubois on 10/17/17.
//
#include <resolv.h>
#include <arpa/inet.h>
#include <netdb.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import "AboutViewController.h"
#import "AppDelegate.h"
#import "AppGroup.h"
#import "CurrentRoot.h"
#import "ExceptionExfiltrator.h"
#import "iOSFS.h"
#import "SceneDelegate.h"
#import "PasteboardDevice.h"
#import "LocationDevice.h"
#import "NSObject+SaneKVO.h"
#import "Roots.h"
#import "TerminalViewController.h"
#import "UserPreferences.h"
#import "UIApplication+OpenURL.h"
#include "kernel/init.h"
#include "kernel/calls.h"
#include "fs/dyndev.h"
#include "fs/devices.h"
#include "fs/path.h"
#if ISH_LINUX
#import "LinuxInterop.h"
#endif
@interface AppDelegate ()
@property BOOL exiting;
@property SCNetworkReachabilityRef reachability;
@end
#if !ISH_LINUX
static void ios_handle_exit(struct task *task, int code) {
// we are interested in init and in children of init
// this is called with pids_lock as an implementation side effect, please do not cite as an example of good API design
if (task->parent != NULL && task->parent->parent != NULL)
return;
// pid should be saved now since task would be freed
pid_t pid = task->pid;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:ProcessExitedNotification
object:nil
userInfo:@{@"pid": @(pid),
@"code": @(code)}];
});
}
static void ios_handle_die(const char *msg) {
NSString *message = [NSString stringWithFormat:@"%s: %s", __func__, msg];
iSHExceptionHandler([[NSException alloc] initWithName:NSGenericException reason:message userInfo:nil]);
}
#elif ISH_LINUX
void ReportPanic(const char *message) {
[NSNotificationCenter.defaultCenter postNotificationName:KernelPanicNotification object:nil userInfo:@{@"message":@(message)}];
}
#endif
static int bootError;
static NSString *const kSkipStartupMessage = @"Skip Startup Message";
@implementation AppDelegate
- (int)boot {
#if !ISH_LINUX
NSURL *root = [Roots.instance rootUrl:Roots.instance.defaultRoot];
int err = mount_root(&fakefs, [root URLByAppendingPathComponent:@"data"].fileSystemRepresentation);
if (err < 0)
return err;
fs_register(&iosfs);
fs_register(&iosfs_unsafe);
// need to do this first so that we can have a valid current for the generic_mknod calls
err = become_first_process();
if (err < 0)
return err;
FsInitialize();
// create some device nodes
// this will do nothing if they already exist
generic_mknodat(AT_PWD, "/dev/tty1", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 1));
generic_mknodat(AT_PWD, "/dev/tty2", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 2));
generic_mknodat(AT_PWD, "/dev/tty3", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 3));
generic_mknodat(AT_PWD, "/dev/tty4", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 4));
generic_mknodat(AT_PWD, "/dev/tty5", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 5));
generic_mknodat(AT_PWD, "/dev/tty6", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 6));
generic_mknodat(AT_PWD, "/dev/tty7", S_IFCHR|0666, dev_make(TTY_CONSOLE_MAJOR, 7));
generic_mknodat(AT_PWD, "/dev/tty", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_TTY_MINOR));
generic_mknodat(AT_PWD, "/dev/console", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_CONSOLE_MINOR));
generic_mknodat(AT_PWD, "/dev/ptmx", S_IFCHR|0666, dev_make(TTY_ALTERNATE_MAJOR, DEV_PTMX_MINOR));
generic_mknodat(AT_PWD, "/dev/null", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_NULL_MINOR));
generic_mknodat(AT_PWD, "/dev/zero", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_ZERO_MINOR));
generic_mknodat(AT_PWD, "/dev/full", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_FULL_MINOR));
generic_mknodat(AT_PWD, "/dev/random", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_RANDOM_MINOR));
generic_mknodat(AT_PWD, "/dev/urandom", S_IFCHR|0666, dev_make(MEM_MAJOR, DEV_URANDOM_MINOR));
generic_mkdirat(AT_PWD, "/dev/pts", 0755);
// Permissions on / have been broken for a while, let's fix them
generic_setattrat(AT_PWD, "/", (struct attr) {.type = attr_mode, .mode = 0755}, false);
// Register clipboard device driver and create device node for it
err = dyn_dev_register(&clipboard_dev, DEV_CHAR, DYN_DEV_MAJOR, DEV_CLIPBOARD_MINOR);
if (err != 0) {
return err;
}
generic_mknodat(AT_PWD, "/dev/clipboard", S_IFCHR|0666, dev_make(DYN_DEV_MAJOR, DEV_CLIPBOARD_MINOR));
err = dyn_dev_register(&location_dev, DEV_CHAR, DYN_DEV_MAJOR, DEV_LOCATION_MINOR);
if (err != 0)
return err;
generic_mknodat(AT_PWD, "/dev/location", S_IFCHR|0666, dev_make(DYN_DEV_MAJOR, DEV_LOCATION_MINOR));
do_mount(&procfs, "proc", "/proc", "", 0);
do_mount(&devptsfs, "devpts", "/dev/pts", "", 0);
iosfs_init(); // let it mount any filesystems from user defaults
[self configureDns];
exit_hook = ios_handle_exit;
die_handler = ios_handle_die;
#if !TARGET_OS_SIMULATOR
NSString *sockTmp = [NSTemporaryDirectory() stringByAppendingString:@"ishsock"];
sock_tmp_prefix = strdup(sockTmp.UTF8String);
#endif
tty_drivers[TTY_CONSOLE_MAJOR] = &ios_console_driver;
set_console_device(TTY_CONSOLE_MAJOR, 1);
err = create_stdio("/dev/console", TTY_CONSOLE_MAJOR, 1);
if (err < 0)
return err;
NSArray<NSString *> *command;
command = UserPreferences.shared.bootCommand;
NSLog(@"%@", command);
char argv[4096];
[Terminal convertCommand:command toArgs:argv limitSize:sizeof(argv)];
const char *envp = "TERM=xterm-256color\0";
err = do_execve(command[0].UTF8String, command.count, argv, envp);
if (err < 0)
return err;
task_start(current);
#else
// On first launch, this will trigger the import of the default root. Make sure to do this before entering the kernel, because it needs to run something on the main thread, and that would deadlock.
[Roots instance];
NSArray<NSString *> *args = @[];
actuate_kernel([args componentsJoinedByString:@" "].UTF8String);
#endif
return 0;
}
#if ISH_LINUX
const char *DefaultRootPath() {
return [Roots.instance rootUrl:Roots.instance.defaultRoot].fileSystemRepresentation;
}
void SyncHostname(void) {
async_do_in_workqueue(^{
char hostname[256];
if (gethostname(hostname, sizeof(hostname)) < 0)
return;
linux_sethostname(hostname);
});
}
#endif
- (void)configureDns {
#if !ISH_LINUX
struct __res_state res;
if (EXIT_SUCCESS != res_ninit(&res)) {
exit(2);
}
NSMutableString *resolvConf = [NSMutableString new];
if (res.dnsrch[0] != NULL) {
[resolvConf appendString:@"search"];
for (int i = 0; res.dnsrch[i] != NULL; i++) {
[resolvConf appendFormat:@" %s", res.dnsrch[i]];
}
[resolvConf appendString:@"\n"];
}
union res_sockaddr_union servers[NI_MAXSERV];
int serversFound = res_getservers(&res, servers, NI_MAXSERV);
char address[NI_MAXHOST];
for (int i = 0; i < serversFound; i ++) {
union res_sockaddr_union s = servers[i];
if (s.sin.sin_len == 0)
continue;
getnameinfo((struct sockaddr *) &s.sin, s.sin.sin_len,
address, sizeof(address),
NULL, 0, NI_NUMERICHOST);
[resolvConf appendFormat:@"nameserver %s\n", address];
}
current = pid_get_task(1);
struct fd *fd = generic_open("/etc/resolv.conf", O_WRONLY_ | O_CREAT_ | O_TRUNC_, 0666);
if (!IS_ERR(fd)) {
fd->ops->write(fd, resolvConf.UTF8String, [resolvConf lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
fd_close(fd);
}
#endif
}
+ (int)bootError {
return bootError;
}
+ (void)maybePresentStartupMessageOnViewController:(UIViewController *)vc {
if ([NSUserDefaults.standardUserDefaults integerForKey:kSkipStartupMessage] >= 1)
return;
if (!FsIsManaged()) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Install iSHs built-in APK?"
message:@"iSH now includes the APK package manager, but it must be manually activated."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Show me how"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[UIApplication openURL:@"https://go.ish.app/get-apk"];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Don't show again"
style:UIAlertActionStyleDefault
handler:nil]];
[vc presentViewController:alert animated:YES completion:nil];
}
[NSUserDefaults.standardUserDefaults setInteger:1 forKey:kSkipStartupMessage];
}
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions {
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
if ([defaults boolForKey:@"hail mary"]) {
[defaults removeObjectForKey:kPreferenceBootCommandKey];
[defaults removeObjectForKey:kPreferenceLaunchCommandKey];
[defaults setBool:NO forKey:@"hail mary"];
}
if ([NSUserDefaults.standardUserDefaults boolForKey:@"recovery"])
return YES;
bootError = [self boot];
#if ISH_LINUX
[NSNotificationCenter.defaultCenter addObserverForName:UIApplicationWillEnterForegroundNotification object:UIApplication.sharedApplication queue:nil usingBlock:^(NSNotification * _Nonnull note) {
SyncHostname();
}];
SyncHostname();
#endif
return YES;
}
void NetworkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
AppDelegate *self = (__bridge AppDelegate *) info;
[self configureDns];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// get the network permissions popup to appear on chinese devices
[[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:@"http://captive.apple.com"]] resume];
if ([NSUserDefaults.standardUserDefaults boolForKey:@"FASTLANE_SNAPSHOT"])
[UIView setAnimationsEnabled:NO];
#if !ISH_LINUX
NSString *ishVersion = [NSString stringWithFormat:@"iSH %@ (%@)",
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[NSBundle.mainBundle objectForInfoDictionaryKey:(NSString *) kCFBundleVersionKey]];
extern const char *proc_ish_version;
proc_ish_version = strdup(ishVersion.UTF8String);
// this defaults key is set when taking app store screenshots
extern const char *uname_hostname_override;
NSString *hostnameOverride = UserPreferences.shared._hostnameOverride;
if (@available(iOS 16.0, *)) { // Hostname obfuscation is in effect
hostnameOverride = hostnameOverride ? hostnameOverride : UserPreferences.shared.hostnameOverride;
}
if (hostnameOverride) {
uname_hostname_override = strdup(hostnameOverride.UTF8String);
}
#endif
[UserPreferences.shared observe:@[@"shouldDisableDimming"] options:NSKeyValueObservingOptionInitial
owner:self usingBlock:^(typeof(self) self) {
dispatch_async(dispatch_get_main_queue(), ^{
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
});
}];
// This code is IPv4 and IPv6 aware: see https://developer.apple.com/library/archive/samplecode/Reachability/Listings/ReadMe_md.html
struct sockaddr_in address = {
.sin_len = sizeof(address),
.sin_family = AF_INET,
};
self.reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *) &address);
SCNetworkReachabilityContext context = {
.info = (__bridge void *) self,
};
SCNetworkReachabilitySetCallback(self.reachability, NetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
if (self.window != nil) {
// For iOS <13, where the app delegate owns the window instead of the scene
if ([NSUserDefaults.standardUserDefaults boolForKey:@"recovery"]) {
UINavigationController *vc = [[UIStoryboard storyboardWithName:@"About" bundle:nil] instantiateInitialViewController];
AboutViewController *avc = (AboutViewController *) vc.topViewController;
avc.recoveryMode = YES;
self.window.rootViewController = vc;
return YES;
}
TerminalViewController *vc = (TerminalViewController *) self.window.rootViewController;
currentTerminalViewController = vc;
[vc startNewSession];
}
return YES;
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)) {
for (UISceneSession *sceneSession in sceneSessions) {
NSString *terminalUUID = sceneSession.stateRestorationActivity.userInfo[@"TerminalUUID"];
[[Terminal terminalWithUUID:[[NSUUID alloc] initWithUUIDString:terminalUUID]] destroy];
}
}
- (void)dealloc {
if (self.reachability != NULL) {
SCNetworkReachabilityUnscheduleFromRunLoop(self.reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFRelease(self.reachability);
}
}
- (void)exitApp {
self.exiting = YES;
id app = [UIApplication sharedApplication];
[app suspend];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
if (self.exiting)
exit(0);
}
@end
#if !ISH_LINUX
NSString *const ProcessExitedNotification = @"ProcessExitedNotification";
#else
NSString *const KernelPanicNotification = @"KernelPanicNotification";
#endif