diff --git a/.gitignore b/.gitignore index 84fce54..380232b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ *.o -/test +*.a +/AltServer + +.vscode/** +.ccls-cache/** diff --git a/.gitmodules b/.gitmodules index d8e6bb7..12433a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "libraries/miniupnpc"] path = libraries/miniupnpc url = https://github.com/NyaMisty/minimalupnpc +[submodule "libraries/AltSign"] + path = libraries/AltSign + url = https://github.com/NyaMisty/AltSign-Linux diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index aaa5b9e..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "files.associations": { - "string.h": "c", - "inttypes.h": "c", - "heartbeat.h": "c", - "sbservices.h": "c", - "utils.h": "c", - "userpref.h": "c", - "common.h": "c", - "xutility": "c", - "debugserver.h": "c" - } -} \ No newline at end of file diff --git a/Makefile b/Makefile index 55545ac..0d20488 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,87 @@ -libimobiledevice_src := $(wildcard libraries/libimobiledevice/src/*.c) $(wildcard libraries/libimobiledevice/common/*.c) -libimobiledevice_src := $(filter-out libraries/libimobiledevice/src/idevice.c, $(libimobiledevice_src)) +PROGRAM := AltServer + +%.c.o : %.c + $(CC) $(CFLAGS) $(EXTRA_FLAGS) -o $@ -c $< + +%.cpp.o : %.cpp + $(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -o $@ -c $< + +CFLAGS := -DHAVE_CONFIG_H -DDEBUG -O0 -g -mno-default +CXXFLAGS = $(CFLAGS) -std=c++17 + +main_src := $(wildcard src/*.c) $(wildcard src/*.cpp) + +libimobiledevice_src := $(wildcard libraries/libimobiledevice/src/*.c) $(wildcard libraries/libimobiledevice/common/*.c) +ifdef NO_USBMUXD_STUB + CFLAGS += -DNO_USBMUXD_STUB + libimobiledevice_src += $(wildcard libraries/libusbmuxd/src/*.c) + libimobiledevice_src += $(wildcard libraries/libusbmuxd/common/*.c) +else + main_src += src/phone/libusbmuxd-stub.c + ifdef NO_UPNP_STUB + CFLAGS += -DNO_UPNP_STUB + else + libimobiledevice_src := $(filter-out libraries/libimobiledevice/src/idevice.c, $(libimobiledevice_src)) + main_src += src/phone/idevice-stub.c + endif +endif + +libimobiledevice_include := -Ilibraries/libimobiledevice/include -Ilibraries/libimobiledevice -Ilibraries/libusbmuxd/include + +libplist_src := $(wildcard libraries/libplist/src/*.c) libraries/libplist/libcnary/node.c libraries/libplist/libcnary/node_list.c +libplist_include := -Ilibraries/libplist/include miniupnpc_src = minissdpc.c miniwget.c minixml.c igd_desc_parse.c minisoap.c \ - miniupnpc.c upnpreplyparse.c upnpcommands.c upnperrors.c \ - connecthostport.c portlistingparse.c receivedata.c upnpdev.c \ - addr_is_reserved.c + miniupnpc.c upnpreplyparse.c upnpcommands.c upnperrors.c \ + connecthostport.c portlistingparse.c receivedata.c upnpdev.c \ + addr_is_reserved.c miniupnpc_src := $(miniupnpc_src:%.c=libraries/miniupnpc/%.c) +miniupnpc_include := -Ilibraries/miniupnpc -src := $(wildcard src/*.c) -src += $(libimobiledevice_src) -src += $(wildcard libraries/libplist/src/*.c) libraries/libplist/libcnary/node.c libraries/libplist/libcnary/node_list.c -src += $(miniupnpc_src) +INC_CFLAGS := -Ilibraries +INC_CFLAGS += $(libimobiledevice_include) +INC_CFLAGS += $(libplist_include) +INC_CFLAGS += $(miniupnpc_include) +INC_CFLAGS += -Ilibraries/AltSign -obj = $(src:.c=.o) +allsrc := $(main_src) +allsrc += $(libimobiledevice_src) +allsrc += $(libplist_src) +allsrc += $(miniupnpc_src) -CFLAGS := -DHAVE_CONFIG_H -DDEBUG -O0 -g -CFLAGS += -Ilibraries -Ilibraries/libimobiledevice/include -Ilibraries/libimobiledevice -CFLAGS += -Ilibraries/libplist/src -Ilibraries/libplist/include -Ilibraries/libplist/libcnary/include -Ilibraries/libusbmuxd/include -CFLAGS += -Ilibraries/miniupnpc +allobj = $(addsuffix .o, $(allsrc)) -LDFLAGS = -lm -lcrypto -lpthread -lssl -lzip +$(addsuffix .o, $(libimobiledevice_src)) : EXTRA_FLAGS := -Ilibraries $(libimobiledevice_include) $(libplist_include) -Ilibraries/libimobiledevice/common -Ilibraries/libusbmuxd/common +libraries/libimobiledevice.a : $(addsuffix .o, $(libimobiledevice_src)) + ar rcs $@ $^ -test: $(obj) +$(addsuffix .o, $(libplist_src)) : EXTRA_FLAGS := -Ilibraries $(libplist_include) -Ilibraries/libplist/libcnary/include -Ilibraries/libplist/src +libraries/libplist.a : $(addsuffix .o, $(libplist_src)) + ar rcs $@ $^ + +# $(miniupnpc_src:.c=.o) : $(miniupnpc_src) +# $(CC) $(CFLAGS) -Ilibraries -c $< +$(addsuffix .o, $(miniupnpc_src)) : EXTRA_FLAGS := -Ilibraries $(miniupnpc_include) +libraries/miniupnp.a : $(addsuffix .o, $(miniupnpc_src)) + ar rcs $@ $^ + +$(addsuffix .o, $(main_src)) : EXTRA_FLAGS := -Ilibraries $(INC_CFLAGS) + +#%.o : %.c +# $(CC) $(CFLAGS) $(INC_CFLAGS) -c $< -o $@ + +lib_AltSign: + $(MAKE) -C libraries/AltSign + +LDFLAGS = libraries/AltSign/AltSign.a -static -lssl -lcrypto -lpthread -lcorecrypto_static -lzip -lm -lz -lcpprest -lboost_system -lboost_filesystem -lstdc++ -lssl -lcrypto -luuid +$(PROGRAM):: lib_AltSign + +$(PROGRAM):: $(addsuffix .o, $(main_src)) libraries/miniupnp.a libraries/libimobiledevice.a libraries/libplist.a $(CC) -o $@ $^ $(LDFLAGS) -.PHONY: clean +.PHONY: clean all lib_AltSign clean: - rm -f $(obj) test \ No newline at end of file + rm -f $(allobj) libraries/*.a $(PROGRAM) + +all: $(PROGRAM) +.DEFAULT_GOAL := all \ No newline at end of file diff --git a/libraries/AltSign b/libraries/AltSign new file mode 160000 index 0000000..dc0dfd2 --- /dev/null +++ b/libraries/AltSign @@ -0,0 +1 @@ +Subproject commit dc0dfd26975f6df071e143194313823e565689b9 diff --git a/src/AltServerApp.cpp b/src/AltServerApp.cpp new file mode 100644 index 0000000..2d22951 --- /dev/null +++ b/src/AltServerApp.cpp @@ -0,0 +1,1032 @@ +// +// AltServerApp.cpp +// AltServer-Windows +// +// Created by Riley Testut on 8/30/19. +// Copyright (c) 2019 Riley Testut. All rights reserved. +// +#include "AltServerApp.h" + +#include "AppleAPI.hpp" +#include "ConnectionManager.hpp" +#include "InstallError.hpp" +#include "Signer.hpp" +#include "DeviceManager.hpp" +#include "Archiver.hpp" +#include "ServerError.hpp" + +#include "AnisetteDataManager.h" + +#include +#include + +#include +#include +#include + +#include + +using namespace utility; // Common utilities like string conversions +using namespace web; // Common features like URIs. +using namespace web::http; // Common HTTP functionality +using namespace web::http::client; // HTTP client features +using namespace concurrency::streams; // Asynchronous streams + +extern std::string temporary_directory(); +extern std::string make_uuid(); +extern std::vector readFile(const char* filename); + + + +AltServerApp* AltServerApp::_instance = nullptr; + +AltServerApp* AltServerApp::instance() +{ + if (_instance == 0) + { + _instance = new AltServerApp(); + } + + return _instance; +} + +AltServerApp::AltServerApp() : _appGroupSemaphore(1) +{ +} + +AltServerApp::~AltServerApp() +{ +} + +void AltServerApp::Start(HWND windowHandle, HINSTANCE instanceHandle) +{ + ConnectionManager::instance()->Start(); + DeviceManager::instance()->Start(); +} + +void AltServerApp::Stop() +{ +} + +pplx::task> AltServerApp::InstallApplication(std::optional filepath, std::shared_ptr installDevice, std::string appleID, std::string password) +{ + return this->_InstallApplication(filepath, installDevice, appleID, password) + .then([=](pplx::task> task) -> pplx::task> { + try + { + auto application = task.get(); + return pplx::create_task([application]() { + return application; + }); + } + catch (APIError& error) + { + if ((APIErrorCode)error.code() == APIErrorCode::InvalidAnisetteData) + { + // Our attempt to re-provision the device as a Mac failed, so reset provisioning and try one more time. + // This appears to happen when iCloud is running simultaneously, and just happens to provision device at same time as AltServer. + AnisetteDataManager::instance()->ResetProvisioning(); + + this->ShowNotification("Registering PC with Apple...", "This may take a few seconds."); + + // Provisioning device can fail if attempted too soon after previous attempt. + // As a hack around this, we wait a bit before trying again. + // 10-11 seconds appears to be too short, so wait for 12 seconds instead. + sleep(12); + + return this->_InstallApplication(filepath, installDevice, appleID, password); + } + else + { + throw; + } + } + }) + .then([=](pplx::task> task) -> std::shared_ptr { + try + { + auto application = task.get(); + + std::stringstream ss; + ss << application->name() << " was successfully installed on " << installDevice->name() << "."; + + this->ShowNotification("Installation Succeeded", ss.str()); + + return application; + } + catch (InstallError& error) + { + if ((InstallErrorCode)error.code() == InstallErrorCode::Cancelled) + { + // Ignore + } + else + { + this->ShowAlert("Installation Failed", error.localizedDescription()); + throw; + } + } + catch (APIError& error) + { + if ((APIErrorCode)error.code() == APIErrorCode::InvalidAnisetteData) + { + AnisetteDataManager::instance()->ResetProvisioning(); + } + + this->ShowAlert("Installation Failed", error.localizedDescription()); + throw; + } + catch (AnisetteError& error) + { + this->ShowAlert("AnisetteData Failed", error.localizedDescription()); + throw; + } + catch (Error& error) + { + this->ShowAlert("Installation Failed", error.localizedDescription()); + throw; + } + catch (std::exception& exception) + { + odslog("Exception:" << exception.what()); + + this->ShowAlert("Installation Failed", exception.what()); + throw; + } + }); +} + +pplx::task> AltServerApp::_InstallApplication(std::optional filepath, std::shared_ptr installDevice, std::string appleID, std::string password) +{ + fs::path destinationDirectoryPath(temporary_directory()); + destinationDirectoryPath.append(make_uuid()); + + auto account = std::make_shared(); + auto app = std::make_shared(); + auto team = std::make_shared(); + auto device = std::make_shared(); + auto appID = std::make_shared(); + auto certificate = std::make_shared(); + auto profile = std::make_shared(); + + auto session = std::make_shared(); + + return pplx::create_task([=]() { + auto anisetteData = AnisetteDataManager::instance()->FetchAnisetteData(); + return this->Authenticate(appleID, password, anisetteData); + }) + .then([=](std::pair, std::shared_ptr> pair) + { + *account = *(pair.first); + *session = *(pair.second); + + odslog("Fetching team..."); + + return this->FetchTeam(account, session); + }) + .then([=](std::shared_ptr tempTeam) + { + odslog("Registering device..."); + + *team = *tempTeam; + return this->RegisterDevice(installDevice, team, session); + }) + .then([=](std::shared_ptr tempDevice) + { + odslog("Fetching certificate..."); + + *device = *tempDevice; + return this->FetchCertificate(team, session); + }) + .then([=](std::shared_ptr tempCertificate) + { + *certificate = *tempCertificate; + + if (filepath.has_value()) + { + odslog("Importing app..."); + + return pplx::create_task([filepath] { + return fs::path(*filepath); + }); + } + else + { + odslog("Downloading app..."); + + // Show alert before downloading AltStore. + this->ShowInstallationNotification("AltStore", device->name()); + return this->DownloadApp(); + } + }) + .then([=](fs::path downloadedAppPath) + { + odslog("Downloaded app!"); + + fs::create_directory(destinationDirectoryPath); + + auto appBundlePath = UnzipAppBundle(downloadedAppPath.string(), destinationDirectoryPath.string()); + auto app = std::make_shared(appBundlePath); + + if (filepath.has_value()) + { + // Show alert after "downloading" local .ipa. + this->ShowInstallationNotification(app->name(), device->name()); + } + else + { + // Remove downloaded app. + + try + { + fs::remove(downloadedAppPath); + } + catch (std::exception& e) + { + odslog("Failed to remove downloaded .ipa." << e.what()); + } + } + + return app; + }) + .then([=](std::shared_ptr tempApp) + { + odslog("Preparing provisioning profiles!"); + *app = *tempApp; + return this->PrepareAllProvisioningProfiles(app, device, team, session); + }) + .then([=](std::map> profiles) + { + odslog("Installing apps!"); + return this->InstallApp(app, device, team, certificate, profiles); + }) + .then([=](pplx::task> task) + { + if (fs::exists(destinationDirectoryPath)) + { + std::string comm = "rm -rf '"; + comm += destinationDirectoryPath.string(); + comm += "'"; + odslog("Removing tmp dir: " << comm); + system(comm.c_str()); + + // if (fs::exists(destinationDirectoryPath)) fs::remove_all(destinationDirectoryPath); + } + + try + { + auto application = task.get(); + return application; + } + catch (LocalizedError& error) + { + if (error.code() == -22421) + { + // Don't know what API call returns this error code, so assume any LocalizedError with -22421 error code + // means invalid anisette data, then throw the correct APIError. + throw APIError(APIErrorCode::InvalidAnisetteData); + } + else if (error.code() == -29004) + { + // Same with -29004, "Environment Mismatch" + throw APIError(APIErrorCode::InvalidAnisetteData); + } + else + { + throw; + } + } + }); +} + +pplx::task AltServerApp::DownloadApp() +{ + fs::path temporaryPath(temporary_directory()); + temporaryPath.append(make_uuid()); + + auto outputFile = std::make_shared(); + + // Open stream to output file. + auto task = fstream::open_ostream((temporaryPath.string())) + .then([=](ostream file) + { + *outputFile = file; + + uri_builder builder("https://cdn.altstore.io/file/altstore/altstore.ipa"); + + http_client client(builder.to_uri()); + return client.request(methods::GET); + }) + .then([=](http_response response) + { + printf("Received download response status code:%u\n", response.status_code()); + + // Write response body into the file. + return response.body().read_to_end(outputFile->streambuf()); + }) + .then([=](size_t) + { + outputFile->close(); + return temporaryPath; + }); + + return task; +} + +pplx::task, std::shared_ptr>> AltServerApp::Authenticate(std::string appleID, std::string password, std::shared_ptr anisetteData) +{ + auto verificationHandler = [=](void)->pplx::task> { + return pplx::create_task([=]() -> std::optional { + std::cout << "Enter two factor code" << std::endl; + std::string _verificationCode = ""; + std::cin >> _verificationCode; + auto verificationCode = std::make_optional(_verificationCode); + _verificationCode = ""; + + return verificationCode; + }); + }; + + return pplx::create_task([=]() { + if (anisetteData == NULL) + { + throw ServerError(ServerErrorCode::InvalidAnisetteData); + } + + return AppleAPI::getInstance()->Authenticate(appleID, password, anisetteData, verificationHandler); + }); +} + +pplx::task> AltServerApp::FetchTeam(std::shared_ptr account, std::shared_ptr session) +{ + auto task = AppleAPI::getInstance()->FetchTeams(account, session) + .then([](std::vector> teams) { + + for (auto& team : teams) + { + if (team->type() == Team::Type::Individual) + { + return team; + } + } + + for (auto& team : teams) + { + if (team->type() == Team::Type::Free) + { + return team; + } + } + + for (auto& team : teams) + { + return team; + } + + throw InstallError(InstallErrorCode::NoTeam); + }); + + return task; +} + +pplx::task> AltServerApp::FetchCertificate(std::shared_ptr team, std::shared_ptr session) +{ + auto task = AppleAPI::getInstance()->FetchCertificates(team, session) + .then([this, team, session](std::vector> certificates) + { + auto certificatesDirectoryPath = this->certificatesDirectoryPath(); + auto cachedCertificatePath = certificatesDirectoryPath.append(team->identifier() + ".p12"); + + std::shared_ptr preferredCertificate = nullptr; + + for (auto& certificate : certificates) + { + if (!certificate->machineName().has_value()) + { + continue; + } + + std::string prefix("AltStore"); + + if (certificate->machineName()->size() < prefix.size()) + { + // Machine name doesn't begin with "AltStore", so ignore. + continue; + } + else + { + auto result = std::mismatch(prefix.begin(), prefix.end(), certificate->machineName()->begin()); + if (result.first != prefix.end()) + { + // Machine name doesn't begin with "AltStore", so ignore. + continue; + } + } + + if (fs::exists(cachedCertificatePath) && certificate->machineIdentifier().has_value()) + { + try + { + auto data = readFile(cachedCertificatePath.string().c_str()); + auto cachedCertificate = std::make_shared(data, *certificate->machineIdentifier()); + + // Manually set machineIdentifier so we can encrypt + embed certificate if needed. + cachedCertificate->setMachineIdentifier(*certificate->machineIdentifier()); + + return pplx::create_task([cachedCertificate] { + return cachedCertificate; + }); + } + catch(std::exception &e) + { + // Ignore cached certificate errors. + odslog("Failed to load cached certificate:" << cachedCertificatePath << ". " << e.what()) + } + } + + preferredCertificate = certificate; + + // Machine name starts with AltStore. + + this->ShowAlert("Installing AltStore with Multiple AltServers Not Supported", + "Please use the same AltServer you previously used with this Apple ID, or else apps installed with other AltServers will stop working.\n\nAre you sure you want to continue? (Ctrl-C to avoid)"); + break; + } + + if (certificates.size() != 0) + { + auto certificate = (preferredCertificate != nullptr) ? preferredCertificate : certificates[0]; + return AppleAPI::getInstance()->RevokeCertificate(certificate, team, session).then([this, team, session](bool success) + { + return this->FetchCertificate(team, session); + }); + } + else + { + std::string machineName = "AltStore"; + + return AppleAPI::getInstance()->AddCertificate(machineName, team, session) + .then([team, session, cachedCertificatePath](std::shared_ptr addedCertificate) + { + auto privateKey = addedCertificate->privateKey(); + if (privateKey == std::nullopt) + { + throw InstallError(InstallErrorCode::MissingPrivateKey); + } + + return AppleAPI::getInstance()->FetchCertificates(team, session) + .then([privateKey, addedCertificate, cachedCertificatePath](std::vector> certificates) + { + std::shared_ptr certificate = nullptr; + + for (auto tempCertificate : certificates) + { + if (tempCertificate->serialNumber() == addedCertificate->serialNumber()) + { + certificate = tempCertificate; + break; + } + } + + if (certificate == nullptr) + { + throw InstallError(InstallErrorCode::MissingCertificate); + } + + certificate->setPrivateKey(privateKey); + + try + { + if (certificate->machineIdentifier().has_value()) + { + auto machineIdentifier = certificate->machineIdentifier(); + + auto encryptedData = certificate->encryptedP12Data(*machineIdentifier); + if (encryptedData.has_value()) + { + std::ofstream fout(cachedCertificatePath.string(), std::ios::out | std::ios::binary); + fout.write((const char*)encryptedData->data(), encryptedData->size()); + fout.close(); + } + } + } + catch (std::exception& e) + { + // Ignore caching certificate errors. + odslog("Failed to cache certificate:" << cachedCertificatePath << ". " << e.what()) + } + + return certificate; + }); + }); + } + }); + + return task; +} + +pplx::task>> AltServerApp::PrepareAllProvisioningProfiles( + std::shared_ptr application, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr session) +{ + return this->PrepareProvisioningProfile(application, std::nullopt, device, team, session) + .then([=](std::shared_ptr profile) { + std::vector>>> tasks; + + auto task = pplx::create_task([application, profile]() -> std::pair> { + return std::make_pair(application->bundleIdentifier(), profile); + }); + tasks.push_back(task); + + for (auto appExtension : application->appExtensions()) + { + auto task = this->PrepareProvisioningProfile(appExtension, application, device, team, session) + .then([appExtension](std::shared_ptr profile) { + return std::make_pair(appExtension->bundleIdentifier(), profile); + }); + tasks.push_back(task); + } + + return pplx::when_all(tasks.begin(), tasks.end()) + .then([tasks](pplx::task>>> task) { + try + { + auto pairs = task.get(); + + std::map> profiles; + for (auto& pair : pairs) + { + profiles[pair.first] = pair.second; + } + + //observe_all_exceptions>>(tasks.begin(), tasks.end()); + return profiles; + } + catch (std::exception& e) + { + //observe_all_exceptions>>(tasks.begin(), tasks.end()); + throw; + } + }); + }); +} + +pplx::task> AltServerApp::PrepareProvisioningProfile( + std::shared_ptr app, + std::optional> parentApp, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr session) +{ + std::string preferredName; + std::string parentBundleID; + + if (parentApp.has_value()) + { + parentBundleID = (*parentApp)->bundleIdentifier(); + preferredName = (*parentApp)->name() + " " + app->name(); + } + else + { + parentBundleID = app->bundleIdentifier(); + preferredName = app->name(); + } + + std::string updatedParentBundleID; + + if (app->isAltStoreApp()) + { + std::stringstream ss; + ss << "com." << team->identifier() << "." << parentBundleID; + + updatedParentBundleID = ss.str(); + } + else + { + updatedParentBundleID = parentBundleID + "." + team->identifier(); + } + + std::string bundleID = std::regex_replace(app->bundleIdentifier(), std::regex(parentBundleID), updatedParentBundleID); + + return this->RegisterAppID(preferredName, bundleID, team, session) + .then([=](std::shared_ptr appID) + { + return this->UpdateAppIDFeatures(appID, app, team, session); + }) + .then([=](std::shared_ptr appID) + { + return this->UpdateAppIDAppGroups(appID, app, team, session); + }) + .then([=](std::shared_ptr appID) + { + return this->FetchProvisioningProfile(appID, device, team, session); + }) + .then([=](std::shared_ptr profile) + { + return profile; + }); +} + +pplx::task> AltServerApp::RegisterAppID(std::string appName, std::string bundleID, std::shared_ptr team, std::shared_ptr session) +{ + auto task = AppleAPI::getInstance()->FetchAppIDs(team, session) + .then([bundleID, appName, team, session](std::vector> appIDs) + { + std::shared_ptr appID = nullptr; + + for (auto tempAppID : appIDs) + { + if (tempAppID->bundleIdentifier() == bundleID) + { + appID = tempAppID; + break; + } + } + + if (appID != nullptr) + { + return pplx::task>([appID]() + { + return appID; + }); + } + else + { + return AppleAPI::getInstance()->AddAppID(appName, bundleID, team, session); + } + }); + + return task; +} + +pplx::task> AltServerApp::UpdateAppIDFeatures(std::shared_ptr appID, std::shared_ptr app, std::shared_ptr team, std::shared_ptr session) +{ + //TODO: Add support for additional features besides app groups. + + std::map altstoreFeatures = appID->features(); + + auto boolNode = plist_new_bool(true); + altstoreFeatures[AppIDFeatureAppGroups] = boolNode; + + //TODO: Only update features if needed. + + std::shared_ptr copiedAppID = std::make_shared(*appID); + copiedAppID->setFeatures(altstoreFeatures); + + plist_free(boolNode); + + return AppleAPI::getInstance()->UpdateAppID(copiedAppID, team, session); +} + +pplx::task> AltServerApp::UpdateAppIDAppGroups(std::shared_ptr appID, std::shared_ptr app, std::shared_ptr team, std::shared_ptr session) +{ + return pplx::create_task([=]() -> pplx::task> { + auto applicationGroupsNode = app->entitlements()["com.apple.security.application-groups"]; + std::vector applicationGroups; + + if (applicationGroupsNode != nullptr) + { + for (int i = 0; i < plist_array_get_size(applicationGroupsNode); i++) + { + auto groupNode = plist_array_get_item(applicationGroupsNode, i); + + char* groupName = nullptr; + plist_get_string_val(groupNode, &groupName); + + applicationGroups.push_back(groupName); + } + } + + if (applicationGroups.size() == 0) + { + auto appGroupsNode = appID->features()["APG3427HIY"]; // App group feature ID + if (appGroupsNode != nullptr) + { + uint8_t isAppGroupsEnabled = 0; + plist_get_bool_val(appGroupsNode, &isAppGroupsEnabled); + + if (!isAppGroupsEnabled) + { + // No app groups, and we haven't enabled the feature already, so don't continue. + return pplx::create_task([appID]() { + return appID; + }); + } + } + } + + this->_appGroupSemaphore.wait(); + + return AppleAPI::getInstance()->FetchAppGroups(team, session) + .then([=](std::vector> fetchedGroups) { + + std::vector>> tasks; + + for (auto groupIdentifier : applicationGroups) + { + std::string adjustedGroupIdentifier = groupIdentifier + "." + team->identifier(); + std::optional> matchingGroup; + + for (auto group : fetchedGroups) + { + if (group->groupIdentifier() == adjustedGroupIdentifier) + { + matchingGroup = group; + break; + } + } + + if (matchingGroup.has_value()) + { + auto task = pplx::create_task([matchingGroup]() { return *matchingGroup; }); + tasks.push_back(task); + } + else + { + std::string name = std::regex_replace("AltStore " + groupIdentifier, std::regex("\\."), " "); + + auto task = AppleAPI::getInstance()->AddAppGroup(name, adjustedGroupIdentifier, team, session); + tasks.push_back(task); + } + } + + return pplx::when_all(tasks.begin(), tasks.end()).then([=](pplx::task>> task) { + try + { + auto groups = task.get(); + //observe_all_exceptions>(tasks.begin(), tasks.end()); + return groups; + } + catch (std::exception& e) + { + //observe_all_exceptions>(tasks.begin(), tasks.end()); + throw; + } + }); + }) + .then([=](std::vector> groups) { + return AppleAPI::getInstance()->AssignAppIDToGroups(appID, groups, team, session); + }) + .then([appID](bool result) { + return appID; + }) + .then([this](pplx::task> task) { + this->_appGroupSemaphore.notify(); + + auto appID = task.get(); + return appID; + }); + }); +} + +pplx::task> AltServerApp::RegisterDevice(std::shared_ptr device, std::shared_ptr team, std::shared_ptr session) +{ + auto task = AppleAPI::getInstance()->FetchDevices(team, device->type(), session) + .then([device, team, session](std::vector> devices) + { + std::shared_ptr matchingDevice = nullptr; + + for (auto tempDevice : devices) + { + odslog("Comparing device: " << tempDevice << "with " << device); + if (tempDevice->identifier() == device->identifier()) + { + matchingDevice = tempDevice; + break; + } + } + + if (matchingDevice != nullptr) + { + return pplx::task>([matchingDevice]() + { + return matchingDevice; + }); + } + else + { + return AppleAPI::getInstance()->RegisterDevice(device->name(), device->identifier(), device->type(), team, session); + } + }); + + return task; +} + +pplx::task> AltServerApp::FetchProvisioningProfile(std::shared_ptr appID, std::shared_ptr device, std::shared_ptr team, std::shared_ptr session) +{ + return AppleAPI::getInstance()->FetchProvisioningProfile(appID, device->type(), team, session); +} + +pplx::task> AltServerApp::InstallApp(std::shared_ptr app, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr certificate, + std::map> profilesByBundleID) +{ + auto prepareInfoPlist = [profilesByBundleID](std::shared_ptr app, plist_t additionalValues){ + auto profile = profilesByBundleID.at(app->bundleIdentifier()); + + fs::path infoPlistPath(app->path()); + infoPlistPath.append("Info.plist"); + + auto data = readFile(infoPlistPath.string().c_str()); + + plist_t plist = nullptr; + plist_from_memory((const char*)data.data(), (int)data.size(), &plist); + if (plist == nullptr) + { + throw InstallError(InstallErrorCode::MissingInfoPlist); + } + + plist_dict_set_item(plist, "CFBundleIdentifier", plist_new_string(profile->bundleIdentifier().c_str())); + plist_dict_set_item(plist, "ALTBundleIdentifier", plist_new_string(app->bundleIdentifier().c_str())); + + if (additionalValues != NULL) + { + plist_dict_merge(&plist, additionalValues); + } + + plist_t entitlements = profile->entitlements(); + if (entitlements != nullptr) + { + plist_t appGroups = plist_copy(plist_dict_get_item(entitlements, "com.apple.security.application-groups")); + plist_dict_set_item(plist, "ALTAppGroups", appGroups); + } + + char* plistXML = nullptr; + uint32_t length = 0; + plist_to_xml(plist, &plistXML, &length); + + std::ofstream fout(infoPlistPath.string(), std::ios::out | std::ios::binary); + fout.write(plistXML, length); + fout.close(); + }; + + return pplx::task>([=]() { + fs::path infoPlistPath(app->path()); + infoPlistPath.append("Info.plist"); + + odslog("Signing: Reading InfoPlist..."); + auto data = readFile(infoPlistPath.string().c_str()); + + plist_t plist = nullptr; + plist_from_memory((const char *)data.data(), (int)data.size(), &plist); + if (plist == nullptr) + { + throw InstallError(InstallErrorCode::MissingInfoPlist); + } + + plist_t additionalValues = plist_new_dict(); + + std::string openAppURLScheme = "altstore-" + app->bundleIdentifier(); + + plist_t allURLSchemes = plist_dict_get_item(plist, "CFBundleURLTypes"); + if (allURLSchemes == nullptr) + { + allURLSchemes = plist_new_array(); + } + else + { + allURLSchemes = plist_copy(allURLSchemes); + } + + plist_t altstoreURLScheme = plist_new_dict(); + plist_dict_set_item(altstoreURLScheme, "CFBundleTypeRole", plist_new_string("Editor")); + plist_dict_set_item(altstoreURLScheme, "CFBundleURLName", plist_new_string(app->bundleIdentifier().c_str())); + + plist_t schemesNode = plist_new_array(); + plist_array_append_item(schemesNode, plist_new_string(openAppURLScheme.c_str())); + plist_dict_set_item(altstoreURLScheme, "CFBundleURLSchemes", schemesNode); + + plist_array_append_item(allURLSchemes, altstoreURLScheme); + plist_dict_set_item(additionalValues, "CFBundleURLTypes", allURLSchemes); + + if (app->isAltStoreApp()) + { + plist_dict_set_item(additionalValues, "ALTDeviceID", plist_new_string(device->identifier().c_str())); + + auto serverID = this->serverID(); + plist_dict_set_item(additionalValues, "ALTServerID", plist_new_string(serverID.c_str())); + + auto machineIdentifier = certificate->machineIdentifier(); + if (machineIdentifier.has_value()) + { + auto encryptedData = certificate->encryptedP12Data(*machineIdentifier); + if (encryptedData.has_value()) + { + plist_dict_set_item(additionalValues, "ALTCertificateID", plist_new_string(certificate->serialNumber().c_str())); + + // Embed encrypted certificate in app bundle. + fs::path certificatePath(app->path()); + certificatePath.append("ALTCertificate.p12"); + + std::ofstream fout(certificatePath.string(), std::ios::out | std::ios::binary); + fout.write((const char*)encryptedData->data(), encryptedData->size()); + fout.close(); + } + } + } + + odslog("Signing: Preparing InfoPlist..."); + prepareInfoPlist(app, additionalValues); + + for (auto appExtension : app->appExtensions()) + { + odslog("Signing: Preparing InfoPlist for extensions..."); + prepareInfoPlist(appExtension, NULL); + } + + odslog("Signing: Preparing provisioning profiles..."); + std::vector> profiles; + std::set profileIdentifiers; + for (auto pair : profilesByBundleID) + { + profiles.push_back(pair.second); + profileIdentifiers.insert(pair.second->bundleIdentifier()); + } + + odslog("Signing: Signing app..."); + Signer signer(team, certificate); + signer.SignApp(app->path(), profiles); + + std::optional> activeProfiles = std::nullopt; + if (team->type() == Team::Type::Free && app->isAltStoreApp()) + { + activeProfiles = profileIdentifiers; + } + + odslog("Signing: Installing app..."); + return DeviceManager::instance()->InstallApp(app->path(), device->identifier(), activeProfiles, [](double progress) { + odslog("Installation Progress: " << progress); + }) + .then([app] { + return app; + }); + }); +} + +void AltServerApp::ShowNotification(std::string title, std::string message) +{ + std::cout << "Notify: " << title << std::endl << " " << message << std::endl; +} + +void AltServerApp::ShowAlert(std::string title, std::string message) +{ + std::cout << "Alert: " << title << std::endl << " " << message << std::endl; + std::cout << "Press any key to continue..." << std::endl; + char a; + std::cin >> a; +} + +void AltServerApp::ShowInstallationNotification(std::string appName, std::string deviceName) +{ + std::stringstream ssTitle; + ssTitle << "Installing " << appName << " to " << deviceName << "..."; + + std::stringstream ssMessage; + ssMessage << "This may take a few seconds."; + + this->ShowNotification(ssTitle.str(), ssMessage.str()); +} + +HWND AltServerApp::windowHandle() const +{ + return _windowHandle; +} + +HINSTANCE AltServerApp::instanceHandle() const +{ + return _instanceHandle; +} + +std::string AltServerApp::serverID() const +{ + return "1234567"; +} + +fs::path AltServerApp::appDataDirectoryPath() const +{ + fs::path altserverDirectoryPath("./AltServerData"); + + if (!fs::exists(altserverDirectoryPath)) + { + fs::create_directory(altserverDirectoryPath); + } + + return altserverDirectoryPath; +} + +fs::path AltServerApp::certificatesDirectoryPath() const +{ + auto appDataPath = this->appDataDirectoryPath(); + auto certificatesDirectoryPath = appDataPath.append("Certificates"); + + if (!fs::exists(certificatesDirectoryPath)) + { + fs::create_directory(certificatesDirectoryPath); + } + + return certificatesDirectoryPath; +} \ No newline at end of file diff --git a/src/AltServerApp.h b/src/AltServerApp.h new file mode 100644 index 0000000..ca86a8e --- /dev/null +++ b/src/AltServerApp.h @@ -0,0 +1,128 @@ +// +// AltServerApp.hpp +// AltServer-Windows +// +// Created by Riley Testut on 8/30/19. +// Copyright (c) 2019 Riley Testut. All rights reserved. +// + +#pragma once + +#include "common.h" + +#include + +#include "Account.hpp" +#include "AppID.hpp" +#include "Application.hpp" +#include "Certificate.hpp" +#include "Device.hpp" +#include "ProvisioningProfile.hpp" +#include "Team.hpp" + +#include "AppleAPISession.h" +#include "AnisetteDataManager.h" + +#include "Semaphore.h" + +#include + +#ifdef _WIN32 +#include +#undef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* prevents inclusion by */ +#include +namespace fs = std::filesystem; +#else +#include +namespace fs = boost::filesystem; +#endif + +class AltServerApp +{ +public: + static AltServerApp *instance(); + + void Start(HWND windowHandle, HINSTANCE instanceHandle); + void Stop(); + void CheckForUpdates(); + + pplx::task> InstallApplication(std::optional filepath, std::shared_ptr device, std::string appleID, std::string password); + + void ShowNotification(std::string title, std::string message); + void ShowAlert(std::string title, std::string message); + + HWND windowHandle() const; + HINSTANCE instanceHandle() const; + + bool automaticallyLaunchAtLogin() const; + void setAutomaticallyLaunchAtLogin(bool launch); + + std::string serverID() const; + void setServerID(std::string serverID); + + bool reprovisionedDevice() const; + void setReprovisionedDevice(bool reprovisionedDevice); + + std::string appleFolderPath() const; + std::string internetServicesFolderPath() const; + std::string applicationSupportFolderPath() const; +private: + AltServerApp(); + ~AltServerApp(); + + static AltServerApp *_instance; + + pplx::task> _InstallApplication(std::optional filepath, std::shared_ptr installDevice, std::string appleID, std::string password); + + bool CheckDependencies(); + bool CheckiCloudDependencies(); + + std::string BrowseForFolder(std::wstring title, std::string folderPath); + + bool _presentedNotification; + + HWND _windowHandle; + HINSTANCE _instanceHandle; + + Semaphore _appGroupSemaphore; + + bool presentedRunningNotification() const; + void setPresentedRunningNotification(bool presentedRunningNotification); + + void setAppleFolderPath(std::string appleFolderPath); + std::string defaultAppleFolderPath() const; + + fs::path appDataDirectoryPath() const; + fs::path certificatesDirectoryPath() const; + + pplx::task DownloadApp(); + + void ShowInstallationNotification(std::string appName, std::string deviceName); + + pplx::task, std::shared_ptr>> Authenticate(std::string appleID, std::string password, std::shared_ptr anisetteData); + pplx::task> FetchTeam(std::shared_ptr account, std::shared_ptr session); + pplx::task> FetchCertificate(std::shared_ptr team, std::shared_ptr session); + pplx::task>> PrepareAllProvisioningProfiles( + std::shared_ptr application, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr session); + pplx::task> PrepareProvisioningProfile( + std::shared_ptr application, + std::optional> parentApp, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr session); + pplx::task> RegisterAppID(std::string appName, std::string identifier, std::shared_ptr team, std::shared_ptr session); + pplx::task> UpdateAppIDFeatures(std::shared_ptr appID, std::shared_ptr app, std::shared_ptr team, std::shared_ptr session); + pplx::task> UpdateAppIDAppGroups(std::shared_ptr appID, std::shared_ptr app, std::shared_ptr team, std::shared_ptr session); + pplx::task> RegisterDevice(std::shared_ptr device, std::shared_ptr team, std::shared_ptr session); + pplx::task> FetchProvisioningProfile(std::shared_ptr appID, std::shared_ptr device, std::shared_ptr team, std::shared_ptr session); + + pplx::task> InstallApp(std::shared_ptr app, + std::shared_ptr device, + std::shared_ptr team, + std::shared_ptr certificate, + std::map> profiles); +}; diff --git a/src/AltServerMain.cpp b/src/AltServerMain.cpp new file mode 100644 index 0000000..895d3d9 --- /dev/null +++ b/src/AltServerMain.cpp @@ -0,0 +1,225 @@ +// HelloWindowsDesktop.cpp +// compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c + +#include "common.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define _T(x) x + +// AltSign +#include "DeviceManager.hpp" +#include "Error.hpp" + +#include "AltServerApp.h" + +#include "PhoneHelper.h" + +#include +#include + +#include +std::string make_uuid() { + uuid_t b; + char out[UUID_STR_LEN] = {0}; + uuid_generate(b); + uuid_unparse_lower(b, out); + return out; +} + +std::string temporary_directory() +{ + return fs::temp_directory_path().string(); +} + +std::vector readFile(const char* filename) +{ + // open the file: + std::ifstream file(filename, std::ios::binary); + + // Stop eating new lines in binary mode!!! + file.unsetf(std::ios::skipws); + + // get its size: + std::streampos fileSize; + + file.seekg(0, std::ios::end); + fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // reserve capacity + std::vector vec; + vec.reserve(fileSize); + + // read the data: + vec.insert(vec.begin(), + std::istream_iterator(file), + std::istream_iterator()); + + return vec; +} + +#include + +boost::asio::io_service io_service; +boost::posix_time::seconds interval(15); // 1 second +std::shared_ptr timer; +void *hbclient; + +void heartbeat_tick(const boost::system::error_code& /*e*/) { + int intervalSec = do_heartbeat(hbclient); + odslog("heartbeat_tick! interval: " << intervalSec); + if (!intervalSec) { + return; + } + interval = boost::posix_time::seconds(intervalSec); + // Reschedule the timer for 1 second in the future: + timer->expires_at(timer->expires_at() + interval); + // Posts the timer event + timer->async_wait(heartbeat_tick); +} + +int setupHeartbeatTimer() { + auto &ioService = crossplat::threadpool::shared_instance().service(); + + timer = std::make_shared(ioService); + timer->expires_from_now(boost::posix_time::seconds(1)); + timer->async_wait(heartbeat_tick); + return 1; +} + +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#include + +int main(int argc, char *argv[]) { + static struct option long_options[] = + { + {"udid", required_argument, 0, 'u'}, + {"ipaddr", required_argument, 0, 'i'}, + {"appleID", required_argument, 0, 'a'}, + {"password", required_argument, 0, 'p'}, + {"pairData", required_argument, 0, 'P'}, + {0, 0, 0, 0} + }; + + char *udid; + char *ipaddr; + char *appleID; + char *password; + char *pairDataFile; + + char *ipaPath; + + while (1) { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + + int c = getopt_long (argc, argv, "u:i:a:p:P:", + long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 'u': + udid = optarg; + break; + case 'i': + ipaddr = optarg; + break; + case 'a': + appleID = optarg; + case 'p': + password = optarg; + break; + case 'P': + pairDataFile = optarg; + break; + + default: + printf("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind == argc) { + printf("Please supply an IPA to install\n"); + } else if (optind + 1 == argc) { + ipaPath = argv[optind]; + } else { + printf("Unknown options: "); + while (optind < argc) + printf("%s ", argv[optind++]); + printf("\n"); + } + + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + srand(time(NULL)); + +#ifndef NO_USBMUXD_STUB + setupPairInfo(udid, ipaddr, pairDataFile); + +#ifndef NO_UPNP_STUB + if (!initUPnP()) { + DEBUG_PRINT("failed to init upnp! exitting..."); + return 1; + } + DEBUG_PRINT("upnp init successfully!"); +#endif + + DEBUG_PRINT("Connect device..."); + if (!initGlobalDevice()) { + DEBUG_PRINT("failed to init device! exitting..."); + return 1; + } + if (!initHeartbeat(&hbclient)) { + DEBUG_PRINT("failed to init heartbeat! exitting..."); + return 1; + } + if (!setupHeartbeatTimer()) { + DEBUG_PRINT("failed to init heartbeat! exitting..."); + return 1; + } + DEBUG_PRINT("heartbeat init successfully!"); + +#endif + + signal(SIGPIPE, SIG_IGN); + + bool installApp = true; + if (installApp) { + odslog("Installing app..."); + std::shared_ptr _selectedDevice = std::make_shared("unknown", udid, Device::Type::All);; + std::optional _ipaFilepath = std::make_optional(ipaPath); + auto task = AltServerApp::instance()->InstallApplication(_ipaFilepath, _selectedDevice, (appleID), (password)); + try + { + task.get(); + } + catch (Error& error) + { + odslog("Error: " << error.domain() << " (" << error.code() << ").") + } + catch (std::exception& exception) + { + odslog("Exception: " << exception.what()); + odslog(boost::stacktrace::stacktrace()); + } + + odslog("Finished!"); + } else { + AltServerApp::instance()->Start(0, 0); + while (1) { + sleep(100); + } + } +} \ No newline at end of file diff --git a/src/AnisetteDataManager.cpp b/src/AnisetteDataManager.cpp new file mode 100644 index 0000000..afb0d06 --- /dev/null +++ b/src/AnisetteDataManager.cpp @@ -0,0 +1,260 @@ +#include "AnisetteDataManager.h" +#include +#include +#include "Error.hpp" +#include "ServerError.hpp" + +#include +#include +#include + +#include "AnisetteData.h" +#include "AltServerApp.h" + +AnisetteDataManager* AnisetteDataManager::_instance = nullptr; + +AnisetteDataManager* AnisetteDataManager::instance() +{ + if (_instance == 0) + { + _instance = new AnisetteDataManager(); + } + + return _instance; +} + +AnisetteDataManager::AnisetteDataManager() : loadedDependencies(false) +{ +} + +AnisetteDataManager::~AnisetteDataManager() +{ +} + +bool AnisetteDataManager::LoadiCloudDependencies() +{ + return true; +} + +bool AnisetteDataManager::LoadDependencies() +{ + return true; +} + +#include + +using namespace web; // Common features like URIs. +using namespace web::http; // Common HTTP functionality +using namespace web::http::client; // HTTP client features + +std::shared_ptr AnisetteDataManager::FetchAnisetteData() +{ + std::string wideURI = ("/anisette/irGb3Quww8zrhgqnzmrx"); + + auto encodedURI = web::uri::encode_uri(wideURI); + uri_builder builder(encodedURI); + + http_request request(methods::GET); + request.set_request_uri(builder.to_string()); + + std::map headers = { + {"User-Agent", "Xcode"}, + }; + + for (auto& pair : headers) + { + if (request.headers().has(pair.first)) + { + request.headers().remove(pair.first); + } + + request.headers().add(pair.first, pair.second); + } + + std::shared_ptr anisetteData = NULL; + + auto client = web::http::client::http_client(U("https://armconverter.com")); + auto task = client.request(request) + .then([=](http_response response) + { + return response.content_ready(); + }) + .then([=](http_response response) + { + odslog("Received response status code: " << response.status_code()); + return response.extract_json(); + }) + .then([&anisetteData](pplx::task previousTask) + { + odslog("parse anisette data ret"); + json::value jsonVal = previousTask.get(); + odslog("Got anisetteData json: " << jsonVal); + std::vector keys = { + "X-Apple-I-MD-M", + "X-Apple-I-MD", + "X-Apple-I-MD-LU", + "X-Apple-I-MD-RINFO", + "X-Mme-Device-Id", + "X-Apple-I-SRL-NO", + "X-MMe-Client-Info", + "X-Apple-I-Client-Time", + "X-Apple-Locale", + "X-Apple-I-TimeZone" + }; + for (auto &key : keys) { + odslog(key << ": " << jsonVal.at(key).as_string().c_str()); + } + + struct tm tm = { 0 }; + strptime(jsonVal.at("X-Apple-I-Client-Time").as_string().c_str(), "%FT%T%z", &tm); + time_t ts = mktime(&tm); + struct timeval tv = {0}; + tv.tv_sec = ts; + + odslog("Building anisetteData obj..."); + anisetteData = std::make_shared( + jsonVal.at("X-Apple-I-MD-M").as_string(), + jsonVal.at("X-Apple-I-MD").as_string(), + jsonVal.at("X-Apple-I-MD-LU").as_string(), + std::atoi(jsonVal.at("X-Apple-I-MD-RINFO").as_string().c_str()), + jsonVal.at("X-Mme-Device-Id").as_string(), + jsonVal.at("X-Apple-I-SRL-NO").as_string(), + jsonVal.at("X-MMe-Client-Info").as_string(), + tv, + jsonVal.at("X-Apple-Locale").as_string(), + jsonVal.at("X-Apple-I-TimeZone").as_string()); + + //IterateJSONValue(); + }); + + task.wait(); + + odslog(*anisetteData); + + return anisetteData; +} + +bool AnisetteDataManager::ReprovisionDevice(std::function provisionCallback) +{ +#if !SPOOF_MAC + provisionCallback(); + return true; +#else + std::string adiDirectoryPath = "C:\\ProgramData\\Apple Computer\\iTunes\\adi"; + + /* Start Provisioning */ + + // Move iCloud's ADI files (so we don't mess with them). + for (const auto& entry : fs::directory_iterator(adiDirectoryPath)) + { + if (entry.path().extension() == ".pb") + { + fs::path backupPath = entry.path(); + backupPath += ".icloud"; + + fs::rename(entry.path(), backupPath); + } + } + + // Copy existing AltServer .pb files into original location to reuse the MID. + for (const auto& entry : fs::directory_iterator(adiDirectoryPath)) + { + if (entry.path().extension() == ".altserver") + { + fs::path path = entry.path(); + path.replace_extension(); + + fs::rename(entry.path(), path); + } + } + + auto cleanUp = [adiDirectoryPath]() { + /* Finish Provisioning */ + + // Backup AltServer ADI files. + for (const auto& entry : fs::directory_iterator(adiDirectoryPath)) + { + // Backup AltStore file + if (entry.path().extension() == ".pb") + { + fs::path backupPath = entry.path(); + backupPath += ".altserver"; + + fs::rename(entry.path(), backupPath); + } + } + + // Copy iCloud ADI files back to original location. + for (const auto& entry : fs::directory_iterator(adiDirectoryPath)) + { + if (entry.path().extension() == ".icloud") + { + // Move backup file to original location + fs::path path = entry.path(); + path.replace_extension(); + + fs::rename(entry.path(), path); + + odslog("Copying iCloud file from: " << entry.path().string() << " to: " << path.string()); + } + } + }; + + // Calling CopyAnisetteData implicitly generates new anisette data, + // using the new client info string we injected. + ObjcObject* error = NULL; + ObjcObject* anisetteDictionary = (ObjcObject*)CopyAnisetteData(NULL, 0x1, &error); + + try + { + if (anisetteDictionary == NULL) + { + odslog("Reprovision Error:" << ((ObjcObject*)error)->description()); + + ObjcObject* localizedDescription = (ObjcObject*)((id(*)(id, SEL))objc_msgSend)(error, sel_registerName("localizedDescription")); + if (localizedDescription) + { + int errorCode = ((int(*)(id, SEL))objc_msgSend)(error, sel_registerName("code")); + throw LocalizedError(errorCode, localizedDescription->description()); + } + else + { + throw ServerError(ServerErrorCode::InvalidAnisetteData); + } + } + + odslog("Reprovisioned Anisette:" << anisetteDictionary->description()); + + AltServerApp::instance()->setReprovisionedDevice(true); + + // Call callback while machine is provisioned for AltServer. + provisionCallback(); + } + catch (std::exception &exception) + { + cleanUp(); + + throw; + } + + cleanUp(); + + return true; +#endif +} + +bool AnisetteDataManager::ResetProvisioning() +{ + std::string adiDirectoryPath = "C:\\ProgramData\\Apple Computer\\iTunes\\adi"; + + // Remove existing AltServer .pb files so we can create new ones next time we provision this device. + for (const auto& entry : fs::directory_iterator(adiDirectoryPath)) + { + if (entry.path().extension() == ".altserver") + { + fs::remove(entry.path()); + } + } + + return true; +} \ No newline at end of file diff --git a/src/AnisetteDataManager.h b/src/AnisetteDataManager.h new file mode 100644 index 0000000..cc4b909 --- /dev/null +++ b/src/AnisetteDataManager.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +#include "Error.hpp" + +class AnisetteData; + +enum class AnisetteErrorCode +{ + iTunesNotInstalled, + iCloudNotInstalled, + MissingApplicationSupportFolder, + MissingAOSKit, + MissingObjc, + MissingFoundation, + InvalidiTunesInstallation, +}; + +class AnisetteError : public Error +{ +public: + AnisetteError(AnisetteErrorCode code) : Error((int)code) + { + } + + virtual std::string domain() const + { + return "com.rileytestut.AltServer.AnisetteError"; + } + + virtual std::string localizedDescription() const + { + if (this->_localizedDescription.size() > 0) + { + return this->_localizedDescription; + } + + switch ((AnisetteErrorCode)this->code()) + { + case AnisetteErrorCode::iTunesNotInstalled: return "iTunes Not Found"; + case AnisetteErrorCode::iCloudNotInstalled: return "iCloud Not Found"; + case AnisetteErrorCode::MissingApplicationSupportFolder: return "Missing 'Application Support' in 'Apple' Folder."; + case AnisetteErrorCode::MissingAOSKit: return "Missing 'AOSKit.dll' in 'Internet Services' Folder."; + case AnisetteErrorCode::MissingFoundation: return "Missing 'Foundation.dll' in 'Apple Application Support' Folder."; + case AnisetteErrorCode::MissingObjc: return "Missing 'objc.dll' in 'Apple Application Support' Folder."; + case AnisetteErrorCode::InvalidiTunesInstallation: return "Invalid iTunes installation."; + } + + return ""; + } + + void setLocalizedDescription(std::string localizedDescription) + { + _localizedDescription = localizedDescription; + } + +private: + std::string _localizedDescription; +}; + +class AnisetteDataManager +{ +public: + static AnisetteDataManager* instance(); + + std::shared_ptr FetchAnisetteData(); + bool LoadDependencies(); + + bool ResetProvisioning(); + +private: + AnisetteDataManager(); + ~AnisetteDataManager(); + + static AnisetteDataManager* _instance; + + bool ReprovisionDevice(std::function provisionCallback); + bool LoadiCloudDependencies(); + + bool loadedDependencies; +}; + diff --git a/src/ClientConnection.cpp b/src/ClientConnection.cpp new file mode 100644 index 0000000..714984a --- /dev/null +++ b/src/ClientConnection.cpp @@ -0,0 +1,430 @@ +#include "ClientConnection.h" + +#include +#include +#include +#include + +#include "DeviceManager.hpp" +#include "AnisetteDataManager.h" +#include "AnisetteData.h" + +#include "ServerError.hpp" + +extern std::string make_uuid(); +extern std::string temporary_directory(); + +using namespace web; + +namespace fs = std::filesystem; + +ClientConnection::ClientConnection() +{ +} + +ClientConnection::~ClientConnection() +{ + this->Disconnect(); +} + +void ClientConnection::Disconnect() +{ +} + +pplx::task ClientConnection::ProcessAppRequest() +{ + auto task = this->ReceiveRequest().then([this](web::json::value request) { + auto identifier = (request["identifier"].as_string()); + + if (identifier == "PrepareAppRequest") + { + return this->ProcessPrepareAppRequest(request); + } + else if (identifier == "AnisetteDataRequest") + { + return this->ProcessAnisetteDataRequest(request); + } + else if (identifier == "InstallProvisioningProfilesRequest") + { + return this->ProcessInstallProfilesRequest(request); + } + else if (identifier == "RemoveProvisioningProfilesRequest") + { + return this->ProcessRemoveProfilesRequest(request); + } + else if (identifier == "RemoveAppRequest") + { + return this->ProcessRemoveAppRequest(request); + } + else + { + throw ServerError(ServerErrorCode::UnknownRequest); + } + }).then([this](pplx::task task) { + try + { + task.get(); + } + catch (std::exception& e) + { + auto errorResponse = this->ErrorResponse(e); + this->SendResponse(errorResponse); + + throw; + } + }); + + return task; +} + +pplx::task ClientConnection::ProcessPrepareAppRequest(web::json::value request) +{ + utility::string_t* filepath = new utility::string_t; + std::string udid = (request["udid"].as_string()); + + return this->ReceiveApp(request).then([this, filepath](std::string path) { + *filepath = (path); + return this->ReceiveRequest(); + }) + .then([this, filepath, udid](web::json::value request) { + std::optional> activeProfiles = std::nullopt; + + if (request.has_array_field("activeProfiles")) + { + activeProfiles = std::set(); + + auto array = request["activeProfiles"].as_array(); + for (auto& value : array) + { + auto bundleIdentifier = value.as_string(); + activeProfiles->insert((bundleIdentifier)); + } + } + + return this->InstallApp((*filepath), udid, activeProfiles); + }) + .then([this, filepath, udid](pplx::task task) { + + if (filepath->size() > 0) + { + try + { + fs::remove(fs::path(*filepath)); + } + catch (std::exception& e) + { + odslog("Failed to remove received .ipa." << e.what()); + } + } + + delete filepath; + + try + { + task.get(); + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("InstallationProgressResponse"); + response["progress"] = json::value::number(1.0); + return this->SendResponse(response); + } + catch (std::exception& exception) + { + throw; + } + }); +} + +pplx::task ClientConnection::ProcessAnisetteDataRequest(web::json::value request) +{ + return pplx::create_task([this, &request]() { + + auto anisetteData = AnisetteDataManager::instance()->FetchAnisetteData(); + if (!anisetteData) + { + throw ServerError(ServerErrorCode::InvalidAnisetteData); + } + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("AnisetteDataResponse"); + response["anisetteData"] = anisetteData->json(); + return this->SendResponse(response); + }); +} + +pplx::task ClientConnection::ReceiveApp(web::json::value request) +{ + auto appSize = request["contentSize"].as_integer(); + std::cout << "Receiving app (" << appSize << " bytes)..." << std::endl; + + return this->ReceiveData(appSize).then([this](std::vector data) { + fs::path filepath = fs::path(temporary_directory()).append(make_uuid() + ".ipa"); + + std::ofstream file(filepath.string(), std::ios::out | std::ios::binary); + copy(data.cbegin(), data.cend(), std::ostreambuf_iterator(file)); + + return filepath.string(); + }); +} + +pplx::task ClientConnection::InstallApp(std::string filepath, std::string udid, std::optional> activeProfiles) +{ + return pplx::create_task([this, filepath, udid, activeProfiles]() { + try { + auto isSending = std::make_shared(); + + return DeviceManager::instance()->InstallApp(filepath, udid, activeProfiles, [this, isSending](double progress) { + if (*isSending) + { + return; + } + + *isSending = true; + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("InstallationProgressResponse"); + response["progress"] = json::value::number(progress); + + this->SendResponse(response).then([isSending](pplx::task task) { + *isSending = false; + }); + }); + } + catch (Error& error) + { + std::cout << error << std::endl; + + throw error; + } + catch (std::exception& e) + { + std::cout << "Exception: " << e.what() << std::endl; + + throw e; + } + std::cout << "Installed app!" << std::endl; + }); +} + +pplx::task ClientConnection::ProcessInstallProfilesRequest(web::json::value request) +{ + std::string udid = (request["udid"].as_string()); + + std::vector> provisioningProfiles; + + auto array = request["provisioningProfiles"].as_array(); + for (auto& value : array) + { + auto encodedData = value.as_string(); + auto data = utility::conversions::from_base64(encodedData); + + auto profile = std::make_shared(data); + if (profile != nullptr) + { + provisioningProfiles.push_back(profile); + } + } + + std::optional> activeProfiles = std::nullopt; + if (request.has_array_field("activeProfiles")) + { + activeProfiles = std::set(); + + auto array = request["activeProfiles"].as_array(); + for (auto& value : array) + { + auto bundleIdentifier = value.as_string(); + activeProfiles->insert((bundleIdentifier)); + } + } + + return DeviceManager::instance()->InstallProvisioningProfiles(provisioningProfiles, udid, activeProfiles) + .then([=](pplx::task task) { + try + { + task.get(); + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("InstallProvisioningProfilesResponse"); + return this->SendResponse(response); + } + catch (std::exception& exception) + { + throw; + } + }); +} + +pplx::task ClientConnection::ProcessRemoveProfilesRequest(web::json::value request) +{ + std::string udid = (request["udid"].as_string()); + + std::set bundleIdentifiers; + + auto array = request["bundleIdentifiers"].as_array(); + for (auto& value : array) + { + auto bundleIdentifier = (value.as_string()); + bundleIdentifiers.insert(bundleIdentifier); + } + + return DeviceManager::instance()->RemoveProvisioningProfiles(bundleIdentifiers, udid) + .then([=](pplx::task task) { + try + { + task.get(); + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("RemoveProvisioningProfilesResponse"); + return this->SendResponse(response); + } + catch (std::exception& exception) + { + throw; + } + }); +} + +pplx::task ClientConnection::ProcessRemoveAppRequest(web::json::value request) +{ + std::string udid = (request["udid"].as_string()); + auto bundleIdentifier = (request["bundleIdentifier"].as_string()); + + return DeviceManager::instance()->RemoveApp(bundleIdentifier, udid) + .then([=](pplx::task task) { + try + { + task.get(); + + auto response = json::value::object(); + response["version"] = json::value::number(1); + response["identifier"] = json::value::string("RemoveAppResponse"); + return this->SendResponse(response); + } + catch (std::exception& exception) + { + throw; + } + }); +} + +web::json::value ClientConnection::ErrorResponse(std::exception& exception) +{ + auto response = json::value::object(); + response["version"] = json::value::number(2); + response["identifier"] = json::value::string("ErrorResponse"); + + auto errorObject = json::value::object(); + + try + { + Error& error = dynamic_cast(exception); + + response["errorCode"] = json::value::number(error.code()); + errorObject["errorCode"] = json::value::number(error.code()); + + if (!error.userInfo().empty()) + { + auto userInfo = json::value::object(); + + for (auto& pair : error.userInfo()) + { + userInfo[(pair.first)] = json::value((pair.second)); + } + + errorObject["userInfo"] = userInfo; + } + } + catch (std::bad_cast) + { + response["errorCode"] = json::value::number((int)ServerErrorCode::Unknown); + errorObject["errorCode"] = json::value::number((int)ServerErrorCode::Unknown); + + auto userInfo = json::value::object(); + + if (std::string(exception.what()) == "vector too long") + { + userInfo["NSLocalizedFailureReason"] = json::value::string("Windows Defender Blocked Installation"); + userInfo["NSLocalizedRecoverySuggestion"] = json::value::string("Disable Windows real-time protection on your computer then try again."); + } + else + { + userInfo["NSLocalizedDescription"] = json::value::string((exception.what())); + userInfo["NSLocalizedFailureReason"] = json::value::string((exception.what())); + } + + errorObject["userInfo"] = userInfo; + } + + response["serverError"] = errorObject; + + return response; +} + +pplx::task ClientConnection::SendResponse(web::json::value json) +{ + auto serializedJSON = json.serialize(); + std::vector responseData(serializedJSON.begin(), serializedJSON.end()); + + int32_t size = (int32_t)responseData.size(); + + std::vector responseSizeData; + + if (responseSizeData.size() < sizeof(size)) + { + responseSizeData.resize(sizeof(size)); + } + + std::memcpy(responseSizeData.data(), &size, sizeof(size)); + + std::cout << "Represented Value: " << *((int32_t*)responseSizeData.data()) << std::endl; + + auto task = this->SendData(responseSizeData) + .then([this, responseData]() mutable { + return this->SendData(responseData); + }) + .then([](pplx::task task) { + try + { + task.get(); + } + catch (Error& error) + { + odslog("Failed to send response. " << error.localizedDescription()); + } + catch (std::exception& exception) + { + odslog("Failed to send response. " << exception.what()); + } + }); + + return task; +} + +pplx::task ClientConnection::ReceiveRequest() +{ + int size = sizeof(uint32_t); + + std::cout << "Receiving request size..." << std::endl; + + auto task = this->ReceiveData(size) + .then([this](std::vector data) { + int expectedBytes = *((int32_t*)data.data()); + std::cout << "Receiving " << expectedBytes << " bytes..." << std::endl; + + return this->ReceiveData(expectedBytes); + }) + .then([](std::vector data) { + std::string jsonString(data.begin(), data.end()); + + auto request = web::json::value::parse(jsonString); + return request; + }); + + return task; +} diff --git a/src/ClientConnection.h b/src/ClientConnection.h new file mode 100644 index 0000000..4e97c5b --- /dev/null +++ b/src/ClientConnection.h @@ -0,0 +1,43 @@ +#pragma once + +#include "common.h" +#include "Device.hpp" + +#include +#include + +#include +#include + +#include +#include + +class ClientConnection +{ +public: + ClientConnection(); + virtual ~ClientConnection(); + + virtual void Disconnect(); + + pplx::task ProcessAppRequest(); + + pplx::task ProcessPrepareAppRequest(web::json::value request); + pplx::task ProcessAnisetteDataRequest(web::json::value request); + pplx::task ProcessInstallProfilesRequest(web::json::value request); + pplx::task ProcessRemoveProfilesRequest(web::json::value request); + pplx::task ProcessRemoveAppRequest(web::json::value request); + + pplx::task SendResponse(web::json::value json); + pplx::task ReceiveRequest(); + + virtual pplx::task SendData(std::vector& data) = 0; + virtual pplx::task> ReceiveData(int size) = 0; + +private: + pplx::task ReceiveApp(web::json::value request); + pplx::task InstallApp(std::string filepath, std::string udid, std::optional> activeProfiles); + + web::json::value ErrorResponse(std::exception& exception); +}; + diff --git a/src/ConnectionManager.cpp b/src/ConnectionManager.cpp new file mode 100644 index 0000000..826c33c --- /dev/null +++ b/src/ConnectionManager.cpp @@ -0,0 +1,340 @@ +// +// ConnectionManager.cpp +// AltServer-Windows +// +// Created by Riley Testut on 8/13/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#include "ConnectionManager.hpp" + +#include +#include + +//#include + +#include + +/* +#include +#include +#include +*/ + +#ifdef HAVE_MDNS +#include "dns_sd.h" +#endif + +#include "AltServerApp.h" +#include "WirelessConnection.h" +#include "DeviceManager.hpp" +#include "Error.hpp" + +#include + +#define WIRED_SERVER_CONNECTION_AVAILABLE_REQUEST "io.altstore.Request.WiredServerConnectionAvailable" +#define WIRED_SERVER_CONNECTION_AVAILABLE_RESPONSE "io.altstore.Response.WiredServerConnectionAvailable" +#define WIRED_SERVER_CONNECTION_START_REQUEST "io.altstore.Request.WiredServerConnectionStart" + +#ifdef HAVE_MDNS +void DNSSD_API ConnectionManagerBonjourRegistrationFinished(DNSServiceRef service, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) +{ + std::cout << "Registered service: " << name << " (Error: " << errorCode << ")" << std::endl; +} +#endif + +void ConnectionManagerConnectedDevice(std::shared_ptr device) +{ + ConnectionManager::instance()->StartNotificationConnection(device); +} + +void ConnectionManagerDisconnectedDevice(std::shared_ptr device) +{ + ConnectionManager::instance()->StopNotificationConnection(device); +} + +ConnectionManager* ConnectionManager::_instance = nullptr; + +ConnectionManager* ConnectionManager::instance() +{ + if (_instance == 0) + { + _instance = new ConnectionManager(); + } + + return _instance; +} + +ConnectionManager::ConnectionManager() +{ + DeviceManager::instance()->setConnectedDeviceCallback(ConnectionManagerConnectedDevice); + DeviceManager::instance()->setDisconnectedDeviceCallback(ConnectionManagerDisconnectedDevice); +} + +void ConnectionManager::Start() +{ + auto listenFunction = [](void) { + ConnectionManager::instance()->Listen(); + }; + + _listeningThread = std::thread(listenFunction); +} + +void ConnectionManager::Disconnect(std::shared_ptr connection) +{ + connection->Disconnect(); + _connections.erase(connection); +} + +void ConnectionManager::StartAdvertising(int socketPort) +{ +#ifdef HAVE_MDNS + DNSServiceRef service = NULL; + uint16_t port = htons(socketPort); + + auto serverID = AltServerApp::instance()->serverID(); + + std::string txtValue("serverID=" + serverID); + char size = txtValue.size(); + + std::vector txtData; + txtData.reserve(size + 1); + txtData.push_back(size); + + for (auto& byte : txtValue) + { + txtData.push_back(byte); + } + + DNSServiceErrorType registrationResult = DNSServiceRegister(&service, 0, 0, NULL, "_altserver._tcp", NULL, NULL, port, txtData.size(), txtData.data(), ConnectionManagerBonjourRegistrationFinished, NULL); + if (registrationResult != kDNSServiceErr_NoError) + { + std::cout << "Bonjour Registration Error: " << registrationResult << std::endl; + return; + } + + int dnssd_socket = DNSServiceRefSockFD(service); + if (dnssd_socket == -1) + { + std::cout << "Failed to retrieve mDNSResponder socket." << std::endl; + } + + this->_mDNSResponderSocket = dnssd_socket; +#endif +} + +void ConnectionManager::Listen() +{ + int socket4 = socket(AF_INET, SOCK_STREAM, 0); + if (socket4 == 0) + { + std::cout << "Failed to create socket." << std::endl; + return; + } + + struct sockaddr_in address4; + memset(&address4, 0, sizeof(address4)); + //address4.sin_len = sizeof(address4); + address4.sin_family = AF_INET; + address4.sin_port = 0; // Choose for us. + address4.sin_addr.s_addr = INADDR_ANY; + + if (bind(socket4, (struct sockaddr *)&address4, sizeof(address4)) < 0) + { + std::cout << "Failed to bind socket." << std::endl; + return; + } + + if (listen(socket4, 0) != 0) + { + std::cout << "Failed to prepare listening socket." << std::endl; + } + + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + + if (getsockname(socket4, (struct sockaddr *)&sin, &len) == -1) + { + std::cout << "Failed to get socket name." << std::endl; + } + + int port4 = ntohs(sin.sin_port); + this->StartAdvertising(port4); + + fd_set input_set; + fd_set copy_set; + + while (true) + { + struct timeval tv; + tv.tv_sec = 1; /* 1 second timeout */ + tv.tv_usec = 0; /* no microseconds. */ + + /* Selection */ + FD_ZERO(&input_set ); /* Empty the FD Set */ + FD_SET(socket4, &input_set); /* Listen to the input descriptor */ + + FD_ZERO(©_set ); /* Empty the FD Set */ + FD_SET(socket4, ©_set); /* Listen to the input descriptor */ + + int ready_for_reading = select(socket4 + 1, &input_set, ©_set, NULL, &tv); + + /* Selection handling */ + if (ready_for_reading > 0) + { + + + struct sockaddr_in clientAddress; + memset(&clientAddress, 0, sizeof(clientAddress)); + + socklen_t addrlen = sizeof(clientAddress); + int other_socket = accept(socket4, (struct sockaddr *)&clientAddress, &addrlen); + + + char *ipaddress = inet_ntoa(((struct sockaddr_in)clientAddress).sin_addr); + int port2 = ntohs(((struct sockaddr_in)clientAddress).sin_port); + //int error = WSAGetLastError(); + int error = errno; + + odslog("Other Socket:" << other_socket << ". Port: " << port2 << ". Error: " << error); + + std::shared_ptr clientConnection(new WirelessConnection(other_socket)); + this->HandleRequest(clientConnection); + } + else if (ready_for_reading == -1) + { + /* Handle the error */ + std::cout << "Uh-oh" << std::endl; + } + else + { + // Do nothing + } + } +} + +void ConnectionManager::StartNotificationConnection(std::shared_ptr device) +{ + odslog("Starting notification connection to device: " << device->name().c_str()); + + DeviceManager::instance()->StartNotificationConnection(device) + .then([=](pplx::task> task) { + std::vector notifications = { WIRED_SERVER_CONNECTION_AVAILABLE_REQUEST, WIRED_SERVER_CONNECTION_START_REQUEST }; + + try + { + auto connection = task.get(); + + connection->StartListening(notifications); + connection->setReceivedNotificationHandler([=](std::string notification) { + this->HandleNotification(notification, connection); + }); + + this->_notificationConnections[device->identifier()] = connection; + } + catch (Error& e) + { + odslog("Failed to start notification connection. " << e.localizedDescription().c_str()); + } + catch (std::exception& e) + { + odslog("Failed to start notification connection. " << e.what()); + } + }); +} + +void ConnectionManager::StopNotificationConnection(std::shared_ptr device) +{ + if (this->notificationConnections().count(device->identifier()) == 0) + { + return; + } + + auto connection = this->notificationConnections()[device->identifier()]; + connection->Disconnect(); + + this->_notificationConnections.erase(device->identifier()); +} + +void ConnectionManager::HandleNotification(std::string notification, std::shared_ptr connection) +{ + if (notification == WIRED_SERVER_CONNECTION_AVAILABLE_REQUEST) + { + try + { + connection->SendNotification(WIRED_SERVER_CONNECTION_AVAILABLE_RESPONSE); + + odslog("Sent wired server connection available response!"); + } + catch (Error& e) + { + odslog("Error sending wired server connection response. " << e.localizedDescription().c_str()); + } + catch (std::exception& e) + { + odslog("Error sending wired server connection response. " << e.what()); + } + } + else if (notification == WIRED_SERVER_CONNECTION_START_REQUEST) + { + DeviceManager::instance()->StartWiredConnection(connection->device()) + .then([=](pplx::task> task) { + try + { + auto wiredConnection = task.get(); + auto connection = std::dynamic_pointer_cast(wiredConnection); + + odslog("Started wired server connection!"); + + this->HandleRequest(wiredConnection); + } + catch (Error& e) + { + odslog("Error starting wired server connection. " << e.localizedDescription().c_str()); + } + catch (std::exception& e) + { + odslog("Error starting wired server connection. " << e.what()); + } + }); + } +} + +void ConnectionManager::HandleRequest(std::shared_ptr clientConnection) +{ + this->_connections.insert(clientConnection); + + clientConnection->ProcessAppRequest().then([=](pplx::task task) { + try + { + task.get(); + + odslog("Finished handling request!"); + } + catch (Error& e) + { + odslog("Failed to handle request:" << e.localizedDescription().c_str()); + } + catch (std::exception& e) + { + odslog("Failed to handle request:" << e.what()); + } + + this->Disconnect(clientConnection); + }); +} + +int ConnectionManager::mDNSResponderSocket() const +{ + return _mDNSResponderSocket; +} + +std::set> ConnectionManager::connections() const +{ + return _connections; +} + +std::map> ConnectionManager::notificationConnections() const +{ + return _notificationConnections; +} diff --git a/src/ConnectionManager.hpp b/src/ConnectionManager.hpp new file mode 100644 index 0000000..3af6199 --- /dev/null +++ b/src/ConnectionManager.hpp @@ -0,0 +1,57 @@ +// +// ConnectionManager.hpp +// AltServer-Windows +// +// Created by Riley Testut on 8/13/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#ifndef ConnectionManager_hpp +#define ConnectionManager_hpp + +#include +#include +#include +#include + +#include "ClientConnection.h" +#include "NotificationConnection.h" + +class ConnectionManager +{ +public: + static ConnectionManager* instance(); + + void Start(); + void Disconnect(std::shared_ptr connection); + +private: + ConnectionManager(); + ~ConnectionManager(); + + static ConnectionManager* _instance; + + std::thread _listeningThread; + + int _mDNSResponderSocket; + std::set> _connections; + std::map> _notificationConnections; + + int mDNSResponderSocket() const; + std::set> connections() const; + std::map> notificationConnections() const; + + void Listen(); + void StartAdvertising(int port); + + void StartNotificationConnection(std::shared_ptr device); + void StopNotificationConnection(std::shared_ptr device); + + void HandleRequest(std::shared_ptr connection); + void HandleNotification(std::string notification, std::shared_ptr connection); + + friend void ConnectionManagerConnectedDevice(std::shared_ptr device); + friend void ConnectionManagerDisconnectedDevice(std::shared_ptr device); +}; + +#endif /* ConnectionManager_hpp */ diff --git a/src/DeviceManager.cpp b/src/DeviceManager.cpp new file mode 100644 index 0000000..745d1f7 --- /dev/null +++ b/src/DeviceManager.cpp @@ -0,0 +1,1454 @@ +// +// DeviceManager.cpp +// AltServer-Windows +// +// Created by Riley Testut on 8/13/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#include "DeviceManager.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include "Archiver.hpp" +#include "ServerError.hpp" +#include "ProvisioningProfile.hpp" +#include "Application.hpp" + + +#define DEVICE_LISTENING_SOCKET 28151 + +void DeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid); +void DeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void* udid); +void DeviceDidChangeConnectionStatus(const idevice_event_t* event, void* user_data); + +namespace fs = std::filesystem; + +extern std::string make_uuid(); +extern std::string temporary_directory(); +extern std::vector readFile(const char* filename); + +idevice_error_t idevice_new_all(idevice_t *idevice, const char *udid) { + return idevice_new_with_options(idevice, udid, (idevice_options)(IDEVICE_LOOKUP_USBMUX | IDEVICE_LOOKUP_NETWORK)); +} + +idevice_error_t idevice_new_ignore_network(idevice_t *idevice, const char *udid) { + return idevice_new_with_options(idevice, udid, IDEVICE_LOOKUP_USBMUX); +} + + +/// Returns a version of 'str' where every occurrence of +/// 'find' is substituted by 'replace'. +/// - Inspired by James Kanze. +/// - http://stackoverflow.com/questions/20406744/ +std::string replace_all( + const std::string& str, // where to work + const std::string& find, // substitute 'find' + const std::string& replace // by 'replace' +) { + using namespace std; + string result; + size_t find_len = find.size(); + size_t pos, from = 0; + while (string::npos != (pos = str.find(find, from))) { + result.append(str, from, pos - from); + result.append(replace); + from = pos + find_len; + } + result.append(str, from, string::npos); + return result; +} + +DeviceManager* DeviceManager::_instance = nullptr; + +DeviceManager* DeviceManager::instance() +{ + if (_instance == 0) + { + _instance = new DeviceManager(); + } + + return _instance; +} + +DeviceManager::DeviceManager() +{ +} + +void DeviceManager::Start() +{ + idevice_event_subscribe(DeviceDidChangeConnectionStatus, NULL); +} + +pplx::task DeviceManager::InstallApp(std::string appFilepath, std::string deviceUDID, std::optional> activeProfiles, std::function progressCompletionHandler) +{ + return pplx::task([=] { + // Enforce only one installation at a time. + this->_mutex.lock(); + + auto UUID = make_uuid(); + + char* uuidString = (char*)malloc(UUID.size() + 1); + strncpy(uuidString, (const char*)UUID.c_str(), UUID.size()); + uuidString[UUID.size()] = '\0'; + + idevice_t device = nullptr; + lockdownd_client_t client = NULL; + instproxy_client_t ipc = NULL; + afc_client_t afc = NULL; + misagent_client_t mis = NULL; + lockdownd_service_descriptor_t service = NULL; + + fs::path temporaryDirectory(temporary_directory()); + temporaryDirectory.append(make_uuid()); + + fs::create_directory(temporaryDirectory); + + auto installedProfiles = std::make_shared>>(); + auto cachedProfiles = std::make_shared>>(); + + auto finish = [this, installedProfiles, cachedProfiles, activeProfiles, temporaryDirectory, &uuidString] + (idevice_t device, lockdownd_client_t client, instproxy_client_t ipc, afc_client_t afc, misagent_client_t mis, lockdownd_service_descriptor_t service) + { + auto cleanUp = [=]() { + instproxy_client_free(ipc); + afc_client_free(afc); + lockdownd_client_free(client); + misagent_client_free(mis); + idevice_free(device); + lockdownd_service_descriptor_free(service); + + free(uuidString); + + this->_mutex.unlock(); + // if (fs::exists(temporaryDirectory)) fs::remove_all(temporaryDirectory); + }; + + try + { + if (activeProfiles.has_value()) + { + // Remove installed provisioning profiles if they're not active. + for (auto& installedProfile : *installedProfiles) + { + if (std::count(activeProfiles->begin(), activeProfiles->end(), installedProfile->bundleIdentifier()) == 0) + { + this->RemoveProvisioningProfile(installedProfile, mis); + } + } + } + + for (auto& pair : *cachedProfiles) + { + BOOL reinstall = true; + + for (auto& installedProfile : *installedProfiles) + { + if (installedProfile->bundleIdentifier() == pair.second->bundleIdentifier()) + { + // Don't reinstall cached profile because it was installed with app. + reinstall = false; + break; + } + } + + if (reinstall) + { + this->InstallProvisioningProfile(pair.second, mis); + } + } + } + catch (std::exception& exception) + { + cleanUp(); + throw; + } + + // Clean up outside scope so if an exception is thrown, we don't + // catch it ourselves again. + cleanUp(); + }; + + try + { + fs::path filepath(appFilepath); + + auto extension = filepath.extension().string(); + std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) { + return std::tolower(c); + }); + + fs::path appBundlePath; + + if (extension == ".app") + { + appBundlePath = filepath; + } + else if (extension == ".ipa") + { + std::cout << "Unzipping .ipa..." << std::endl; + appBundlePath = UnzipAppBundle(filepath.string(), temporaryDirectory.string()); + } + else + { + throw SignError(SignErrorCode::InvalidApp); + } + + std::shared_ptr application = std::make_shared(appBundlePath.string()); + if (application == NULL) + { + throw SignError(SignErrorCode::InvalidApp); + } + + if (application->provisioningProfile()) + { + installedProfiles->push_back(application->provisioningProfile()); + } + + for (auto& appExtension : application->appExtensions()) + { + if (appExtension->provisioningProfile()) + { + installedProfiles->push_back(appExtension->provisioningProfile()); + } + } + + odslog("InstallApp: Finding Device...") + /* Find Device */ + if (idevice_new_all(&device, deviceUDID.c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + odslog("InstallApp: Connecting Device...") + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + odslog("InstallApp: Starting instproxy...") + /* Connect to Installation Proxy */ + if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + odslog("InstallApp: Connecting instproxy...") + if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (service) + { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + + odslog("InstallApp: Starting misagent...") + /* Connect to Misagent */ + // Must connect now, since if we take too long writing files to device, connecting may fail later when managing profiles. + if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + odslog("InstallApp: Connecting misagent...") + if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + + odslog("InstallApp: Starting afc...") + /* Connect to AFC service */ + if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + odslog("InstallApp: Connecting afc...") + if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + odslog("InstallApp: Preparing to write files to device...") + fs::path stagingPath("PublicStaging"); + + /* Prepare for installation */ + char** files = NULL; + if (afc_get_file_info(afc, (const char*)stagingPath.c_str(), &files) != AFC_E_SUCCESS) + { + if (afc_make_directory(afc, (const char*)stagingPath.c_str()) != AFC_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceWriteFailed); + } + } + + if (files) + { + int i = 0; + + while (files[i]) + { + free(files[i]); + i++; + } + + free(files); + } + + std::cout << "Writing to device..." << std::endl; + + plist_t options = instproxy_client_options_new(); + instproxy_client_options_add(options, "PackageType", "Developer", NULL); + + fs::path destinationPath = stagingPath.append(appBundlePath.filename().string()); + + int numberOfFiles = 0; + for (auto& item : fs::recursive_directory_iterator(appBundlePath)) + { + if (item.is_regular_file()) + { + numberOfFiles++; + } + } + + int writtenFiles = 0; + + try + { + this->WriteDirectory(afc, appBundlePath.string(), destinationPath.string(), [&writtenFiles, numberOfFiles, progressCompletionHandler](std::string filepath) { + writtenFiles++; + + double progress = (double)writtenFiles / (double)numberOfFiles; + double weightedProgress = progress * 0.75; + progressCompletionHandler(weightedProgress); + }); + } + catch (ServerError& e) + { + if (application->bundleIdentifier().find("science.xnu.undecimus") != std::string::npos) + { + auto userInfo = e.userInfo(); + userInfo["NSLocalizedRecoverySuggestion"] = "Make sure Windows real-time protection is disabled on your computer then try again."; + + throw ServerError((ServerErrorCode)e.code(), userInfo); + } + else + { + throw; + } + } + catch (std::exception& exception) + { + if (application->bundleIdentifier().find("science.xnu.undecimus") != std::string::npos) + { + std::map userInfo = { + { "NSLocalizedDescription", exception.what() }, + { "NSLocalizedRecoverySuggestion", "Make sure Windows real-time protection is disabled on your computer then try again." } + }; + + if (std::string(exception.what()) == std::string("vector too long")) + { + userInfo["NSLocalizedFailureReason"] = "Windows Defender Blocked Installation"; + } + else + { + userInfo["NSLocalizedFailureReason"] = exception.what(); + } + + throw ServerError(ServerErrorCode::Unknown, userInfo); + } + else + { + throw; + } + } + + std::cout << "Finished writing to device." << std::endl; + + + if (service) + { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + /* Provisioning Profiles */ + bool shouldManageProfiles = (activeProfiles.has_value() || (application->provisioningProfile() != NULL && application->provisioningProfile()->isFreeProvisioningProfile())); + if (shouldManageProfiles) + { + // Free developer account was used to sign this app, so we need to remove all + // provisioning profiles in order to remain under sideloaded app limit. + + auto removedProfiles = this->RemoveAllFreeProvisioningProfilesExcludingBundleIdentifiers({}, mis); + for (auto& pair : removedProfiles) + { + if (activeProfiles.has_value()) + { + if (activeProfiles->count(pair.first) > 0) + { + // Only cache active profiles to reinstall afterwards. + (*cachedProfiles)[pair.first] = pair.second; + } + } + else + { + // Cache all profiles to reinstall afterwards if we didn't provide activeProfiles. + (*cachedProfiles)[pair.first] = pair.second; + } + } + } + + lockdownd_client_free(client); + client = NULL; + + std::mutex waitingMutex; + std::condition_variable cv; + + std::optional serverError = std::nullopt; + std::optional localizedError = std::nullopt; + + bool didBeginInstalling = false; + bool didFinishInstalling = false; + + this->_installationProgressHandlers[UUID] = [device, client, ipc, afc, mis, service, finish, progressCompletionHandler, + &waitingMutex, &cv, &didBeginInstalling, &didFinishInstalling, &serverError, &localizedError](double progress, int resultCode, char *name, char *description) { + double weightedProgress = progress * 0.25; + double adjustedProgress = weightedProgress + 0.75; + + if (progress == 0 && didBeginInstalling) + { + if (resultCode != 0 || name != NULL) + { + if (resultCode == -402620383) + { + std::map userInfo = { + { "NSLocalizedRecoverySuggestion", "Make sure 'Offload Unused Apps' is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps." } + }; + serverError = std::make_optional(ServerErrorCode::MaximumFreeAppLimitReached, userInfo); + } + else + { + std::string errorName(name); + + if (errorName == "DeviceOSVersionTooLow") + { + serverError = std::make_optional(ServerErrorCode::UnsupportediOSVersion); + } + else + { + localizedError = std::make_optional(resultCode, description); + } + } + } + + std::lock_guard lock(waitingMutex); + didFinishInstalling = true; + cv.notify_all(); + } + else + { + progressCompletionHandler(adjustedProgress); + } + + didBeginInstalling = true; + }; + + auto narrowDestinationPath = destinationPath.string(); + std::replace(narrowDestinationPath.begin(), narrowDestinationPath.end(), '\\', '/'); + + instproxy_install(ipc, narrowDestinationPath.c_str(), options, DeviceManagerUpdateStatus, uuidString); + instproxy_client_options_free(options); + + // Wait until we're finished installing; + std::unique_lock lock(waitingMutex); + cv.wait(lock, [&didFinishInstalling] { return didFinishInstalling; }); + + lock.unlock(); + + if (serverError.has_value()) + { + throw serverError.value(); + } + + if (localizedError.has_value()) + { + throw localizedError.value(); + } + } + catch (std::exception& exception) + { + try + { + // MUST finish so we restore provisioning profiles. + finish(device, client, ipc, afc, mis, service); + } + catch (std::exception& e) + { + // Ignore since we already caught an exception during installation. + } + + throw; + } + + // Call finish outside try-block so if an exception is thrown, we don't + // catch it ourselves and "finish" again. + finish(device, client, ipc, afc, mis, service); + }); +} + +void DeviceManager::WriteDirectory(afc_client_t client, std::string directoryPath, std::string destinationPath, std::function wroteFileCallback) +{ + std::replace(destinationPath.begin(), destinationPath.end(), '\\', '/'); + + afc_make_directory(client, destinationPath.c_str()); + + for (auto& file : fs::directory_iterator(directoryPath)) + { + auto filepath = file.path(); + + if (fs::is_directory(filepath)) + { + auto destinationDirectoryPath = fs::path(destinationPath).append(filepath.filename().string()); + this->WriteDirectory(client, filepath.string(), destinationDirectoryPath.string(), wroteFileCallback); + } + else + { + auto destinationFilepath = fs::path(destinationPath).append(filepath.filename().string()); + this->WriteFile(client, filepath.string(), destinationFilepath.string(), wroteFileCallback); + } + } +} + +void DeviceManager::WriteFile(afc_client_t client, std::string filepath, std::string destinationPath, std::function wroteFileCallback) +{ + std::replace(destinationPath.begin(), destinationPath.end(), '\\', '/'); + destinationPath = replace_all(destinationPath, "__colon__", ":"); + + odslog("Writing File: " << filepath.c_str() << " to: " << destinationPath.c_str()); + + auto data = readFile(filepath.c_str()); + + uint64_t af = 0; + if ((afc_file_open(client, destinationPath.c_str(), AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || af == 0) + { + throw ServerError(ServerErrorCode::DeviceWriteFailed); + } + + uint32_t bytesWritten = 0; + + while (bytesWritten < data.size()) + { + uint32_t count = 0; + + if (afc_file_write(client, af, (const char *)data.data() + bytesWritten, (uint32_t)data.size() - bytesWritten, &count) != AFC_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceWriteFailed); + } + + bytesWritten += count; + } + + if (bytesWritten != data.size()) + { + throw ServerError(ServerErrorCode::DeviceWriteFailed); + } + + afc_file_close(client, af); + + wroteFileCallback(filepath); +} + +pplx::task DeviceManager::RemoveApp(std::string bundleIdentifier, std::string deviceUDID) +{ + return pplx::task([=] { + idevice_t device = NULL; + lockdownd_client_t client = NULL; + instproxy_client_t ipc = NULL; + lockdownd_service_descriptor_t service = NULL; + + auto cleanUp = [&]() { + if (service) { + lockdownd_service_descriptor_free(service); + } + + if (ipc) { + instproxy_client_free(ipc); + } + + if (client) { + lockdownd_client_free(client); + } + + if (device) { + idevice_free(device); + } + }; + + try + { + /* Find Device */ + if (idevice_new_all(&device, deviceUDID.c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + /* Connect to Installation Proxy */ + if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (service) + { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + auto UUID = make_uuid(); + + char* uuidString = (char*)malloc(UUID.size() + 1); + strncpy(uuidString, (const char*)UUID.c_str(), UUID.size()); + uuidString[UUID.size()] = '\0'; + + std::mutex waitingMutex; + std::condition_variable cv; + + std::optional serverError = std::nullopt; + + bool didFinishInstalling = false; + + this->_deletionCompletionHandlers[UUID] = [this, &waitingMutex, &cv, &didFinishInstalling, &serverError, &uuidString] + (bool success, int errorCode, char* errorName, char* errorDescription) { + if (!success) + { + std::map userInfo = { + { "NSLocalizedFailure", ServerError(ServerErrorCode::AppDeletionFailed).localizedDescription() }, + { "NSLocalizedFailureReason", errorDescription } + }; + serverError = std::make_optional(ServerErrorCode::AppDeletionFailed, userInfo); + } + + std::lock_guard lock(waitingMutex); + didFinishInstalling = true; + cv.notify_all(); + + free(uuidString); + }; + + instproxy_uninstall(ipc, bundleIdentifier.c_str(), NULL, DeviceManagerUpdateAppDeletionStatus, uuidString); + + // Wait until we're finished installing; + std::unique_lock lock(waitingMutex); + cv.wait(lock, [&didFinishInstalling] { return didFinishInstalling; }); + + lock.unlock(); + + if (serverError.has_value()) + { + throw serverError.value(); + } + + cleanUp(); + } + catch (std::exception& exception) { + cleanUp(); + throw; + } + }); +} + +pplx::task> DeviceManager::StartWiredConnection(std::shared_ptr altDevice) +{ + return pplx::create_task([=]() -> std::shared_ptr { + idevice_t device = NULL; + idevice_connection_t connection = NULL; + + /* Find Device */ + if (idevice_new_ignore_network(&device, altDevice->identifier().c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + /* Connect to Listening Socket */ + if (idevice_connect(device, DEVICE_LISTENING_SOCKET, &connection) != IDEVICE_E_SUCCESS) + { + idevice_free(device); + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + idevice_free(device); + + auto wiredConnection = std::make_shared(altDevice, connection); + return wiredConnection; + }); +} + +pplx::task DeviceManager::InstallProvisioningProfiles(std::vector> provisioningProfiles, std::string deviceUDID, std::optional> activeProfiles) +{ + return pplx::task([=] { + // Enforce only one installation at a time. + this->_mutex.lock(); + + idevice_t device = NULL; + lockdownd_client_t client = NULL; + afc_client_t afc = NULL; + misagent_client_t mis = NULL; + lockdownd_service_descriptor_t service = NULL; + + auto cleanUp = [&]() { + if (service) { + lockdownd_service_descriptor_free(service); + } + + if (mis) { + misagent_client_free(mis); + } + + if (afc) { + afc_client_free(afc); + } + + if (client) { + lockdownd_client_free(client); + } + + if (device) { + idevice_free(device); + } + + this->_mutex.unlock(); + }; + + try + { + /* Find Device */ + if (idevice_new_all(&device, deviceUDID.c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + /* Connect to Misagent */ + if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (activeProfiles.has_value()) + { + // Remove all non-active free provisioning profiles. + + auto excludedBundleIdentifiers = activeProfiles.value(); + for (auto& profile : provisioningProfiles) + { + // Ensure we DO remove old versions of profiles we're about to install, even if they are active. + excludedBundleIdentifiers.erase(profile->bundleIdentifier()); + } + + this->RemoveAllFreeProvisioningProfilesExcludingBundleIdentifiers(excludedBundleIdentifiers, mis); + } + else + { + // Remove only older versions of provisioning profiles we're about to install. + + std::set bundleIdentifiers; + for (auto& profile : provisioningProfiles) + { + bundleIdentifiers.insert(profile->bundleIdentifier()); + } + + this->RemoveProvisioningProfiles(bundleIdentifiers, mis); + } + + for (auto& provisioningProfile : provisioningProfiles) + { + this->InstallProvisioningProfile(provisioningProfile, mis); + } + + cleanUp(); + } + catch (std::exception &exception) + { + cleanUp(); + throw; + } + }); +} + +pplx::task DeviceManager::RemoveProvisioningProfiles(std::set bundleIdentifiers, std::string deviceUDID) +{ + return pplx::task([=] { + // Enforce only one removal at a time. + this->_mutex.lock(); + + idevice_t device = NULL; + lockdownd_client_t client = NULL; + afc_client_t afc = NULL; + misagent_client_t mis = NULL; + lockdownd_service_descriptor_t service = NULL; + + auto cleanUp = [&]() { + if (service) { + lockdownd_service_descriptor_free(service); + } + + if (mis) { + misagent_client_free(mis); + } + + if (afc) { + afc_client_free(afc); + } + + if (client) { + lockdownd_client_free(client); + } + + if (device) { + idevice_free(device); + } + + this->_mutex.unlock(); + }; + + try + { + /* Find Device */ + if (idevice_new_all(&device, deviceUDID.c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + /* Connect to Misagent */ + if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) + { + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + this->RemoveProvisioningProfiles(bundleIdentifiers, mis); + + cleanUp(); + } + catch (std::exception& exception) + { + cleanUp(); + throw; + } + }); +} + +std::map> DeviceManager::RemoveProvisioningProfiles(std::set bundleIdentifiers, misagent_client_t mis) +{ + return this->RemoveAllProvisioningProfiles(bundleIdentifiers, std::nullopt, false, mis); +} + +std::map> DeviceManager::RemoveAllFreeProvisioningProfilesExcludingBundleIdentifiers(std::set excludedBundleIdentifiers, misagent_client_t mis) +{ + return this->RemoveAllProvisioningProfiles(std::nullopt, excludedBundleIdentifiers, true, mis); +} + +std::map> DeviceManager::RemoveAllProvisioningProfiles(std::optional> includedBundleIdentifiers, std::optional> excludedBundleIdentifiers, bool limitedToFreeProfiles, misagent_client_t mis) +{ + std::map> ignoredProfiles; + std::map> removedProfiles; + + auto provisioningProfiles = this->CopyProvisioningProfiles(mis); + + for (auto& provisioningProfile : provisioningProfiles) + { + if (limitedToFreeProfiles && !provisioningProfile->isFreeProvisioningProfile()) + { + continue; + } + + if (includedBundleIdentifiers.has_value() && includedBundleIdentifiers->count(provisioningProfile->bundleIdentifier()) == 0) + { + continue; + } + + if (excludedBundleIdentifiers.has_value() && excludedBundleIdentifiers->count(provisioningProfile->bundleIdentifier()) > 0) + { + // This provisioning profile has an excluded bundle identifier. + // Ignore it, unless we've already ignored one with the same bundle identifier, + // in which case remove whichever profile is the oldest. + + auto previousProfile = ignoredProfiles[provisioningProfile->bundleIdentifier()]; + if (previousProfile != NULL) + { + auto expirationDateA = provisioningProfile->expirationDate(); + auto expirationDateB = previousProfile->expirationDate(); + + // We've already ignored a profile with this bundle identifier, + // so make sure we only ignore the newest one and remove the oldest one. + BOOL newerThanPreviousProfile = (timercmp(&expirationDateA, &expirationDateB, >) != 0); + auto oldestProfile = newerThanPreviousProfile ? previousProfile : provisioningProfile; + auto newestProfile = newerThanPreviousProfile ? provisioningProfile : previousProfile; + + ignoredProfiles[provisioningProfile->bundleIdentifier()] = newestProfile; + + // Don't cache this profile or else it will be reinstalled, so just remove it without caching. + this->RemoveProvisioningProfile(oldestProfile, mis); + } + else + { + ignoredProfiles[provisioningProfile->bundleIdentifier()] = provisioningProfile; + } + + continue; + } + + auto preferredProfile = removedProfiles[provisioningProfile->bundleIdentifier()]; + if (preferredProfile != nullptr) + { + auto expirationDateA = provisioningProfile->expirationDate(); + auto expirationDateB = preferredProfile->expirationDate(); + + if (timercmp(&expirationDateA, &expirationDateB, > ) != 0) + { + // provisioningProfile exires later than preferredProfile, so use provisioningProfile instead. + removedProfiles[provisioningProfile->bundleIdentifier()] = provisioningProfile; + } + } + else + { + removedProfiles[provisioningProfile->bundleIdentifier()] = provisioningProfile; + } + + this->RemoveProvisioningProfile(provisioningProfile, mis); + } + + return removedProfiles; +} + +void DeviceManager::InstallProvisioningProfile(std::shared_ptr profile, misagent_client_t mis) +{ + plist_t pdata = plist_new_data((const char*)profile->data().data(), profile->data().size()); + + misagent_error_t result = misagent_install(mis, pdata); + plist_free(pdata); + + if (result == MISAGENT_E_SUCCESS) + { + odslog("Installed profile: " << (profile->bundleIdentifier()) << " (" << (profile->uuid()) << ")"); + } + else + { + int statusCode = misagent_get_status_code(mis); + odslog("Failed to install provisioning profile: " << (profile->bundleIdentifier()) << " (" << (profile->uuid()) << "). Error code: " << statusCode); + + switch (statusCode) + { + case -402620383: + { + std::map userInfo = { + { "NSLocalizedRecoverySuggestion", "Make sure 'Offload Unused Apps' is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps." } + }; + throw ServerError(ServerErrorCode::MaximumFreeAppLimitReached, userInfo); + } + + default: + { + std::ostringstream oss; + oss << "Could not install profile '" << profile->bundleIdentifier() << "'"; + + std::string localizedFailure = oss.str(); + + std::map userInfo = { + { LocalizedFailureErrorKey, localizedFailure }, + { ProvisioningProfileBundleIDErrorKey, profile->bundleIdentifier() }, + { UnderlyingErrorCodeErrorKey, std::to_string(statusCode) } + }; + + throw ServerError(ServerErrorCode::UnderlyingError, userInfo); + } + } + } +} + +void DeviceManager::RemoveProvisioningProfile(std::shared_ptr profile, misagent_client_t mis) +{ + std::string uuid = profile->uuid(); + std::transform(uuid.begin(), uuid.end(), uuid.begin(), [](unsigned char c) { return std::tolower(c); }); + + misagent_error_t result = misagent_remove(mis, uuid.c_str()); + if (result == MISAGENT_E_SUCCESS) + { + odslog("Removed profile: " << (profile->bundleIdentifier()) << " (" << (profile->uuid()) << ")"); + } + else + { + int statusCode = misagent_get_status_code(mis); + odslog("Failed to remove provisioning profile: " << (profile->bundleIdentifier()) << " (" << (profile->uuid()) << "). Error code: " << statusCode); + + switch (statusCode) + { + case -402620405: + { + std::map userInfo = { + { ProvisioningProfileBundleIDErrorKey, profile->bundleIdentifier() }, + }; + + throw ServerError(ServerErrorCode::ProfileNotFound, userInfo); + } + + default: + { + std::ostringstream oss; + oss << "Could not remove profile '" << profile->bundleIdentifier() << "'"; + + std::string localizedFailure = oss.str(); + + std::map userInfo = { + { LocalizedFailureErrorKey, localizedFailure }, + { ProvisioningProfileBundleIDErrorKey, profile->bundleIdentifier() }, + { UnderlyingErrorCodeErrorKey, std::to_string(statusCode) } + }; + + throw ServerError(ServerErrorCode::UnderlyingError, userInfo); + } + } + } +} + +std::vector> DeviceManager::CopyProvisioningProfiles(misagent_client_t mis) +{ + std::vector> provisioningProfiles; + + plist_t profiles = NULL; + + if (misagent_copy_all(mis, &profiles) != MISAGENT_E_SUCCESS) + { + int statusCode = misagent_get_status_code(mis); + + std::string localizedFailure = "Could not copy provisioning profiles."; + + std::map userInfo = { + { LocalizedFailureErrorKey, localizedFailure }, + { UnderlyingErrorCodeErrorKey, std::to_string(statusCode) } + }; + + throw ServerError(ServerErrorCode::UnderlyingError, userInfo); + } + + uint32_t profileCount = plist_array_get_size(profiles); + for (int i = 0; i < profileCount; i++) + { + plist_t profile = plist_array_get_item(profiles, i); + if (plist_get_node_type(profile) != PLIST_DATA) + { + continue; + } + + char* bytes = NULL; + uint64_t length = 0; + + plist_get_data_val(profile, &bytes, &length); + if (bytes == NULL) + { + continue; + } + + std::vector data; + data.reserve(length); + + for (int i = 0; i < length; i++) + { + data.push_back(bytes[i]); + } + + auto provisioningProfile = std::make_shared(data); + provisioningProfiles.push_back(provisioningProfile); + } + + plist_free(profiles); + + return provisioningProfiles; +} + +pplx::task> DeviceManager::StartNotificationConnection(std::shared_ptr altDevice) +{ + return pplx::create_task([=]() -> std::shared_ptr { + idevice_t device = NULL; + lockdownd_client_t lockdownClient = NULL; + lockdownd_service_descriptor_t service = NULL; + np_client_t client = NULL; + + /* Find Device */ + if (idevice_new_ignore_network(&device, altDevice->identifier().c_str()) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::DeviceNotFound); + } + + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &lockdownClient, "altserver") != LOCKDOWN_E_SUCCESS) + { + idevice_free(device); + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + /* Connect to Notification Proxy */ + if ((lockdownd_start_service(lockdownClient, "com.apple.mobile.notification_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) + { + lockdownd_client_free(lockdownClient); + idevice_free(device); + + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + /* Connect to Client */ + if (np_client_new(device, service, &client) != NP_E_SUCCESS) + { + lockdownd_service_descriptor_free(service); + lockdownd_client_free(lockdownClient); + idevice_free(device); + + throw ServerError(ServerErrorCode::ConnectionFailed); + } + + lockdownd_service_descriptor_free(service); + lockdownd_client_free(lockdownClient); + idevice_free(device); + + auto notificationConnection = std::make_shared(altDevice, client); + return notificationConnection; + }); +} + +std::vector> DeviceManager::connectedDevices() const +{ + auto devices = this->availableDevices(false); + return devices; +} + +std::vector> DeviceManager::availableDevices() const +{ + auto devices = this->availableDevices(true); + return devices; +} + +std::vector> DeviceManager::availableDevices(bool includeNetworkDevices) const +{ + std::vector> availableDevices; + + int count = 0; + char **udids = NULL; + if (idevice_get_device_list(&udids, &count) < 0) + { + fprintf(stderr, "ERROR: Unable to retrieve device list!\n"); + return availableDevices; + } + + for (int i = 0; i < count; i++) + { + char *udid = udids[i]; + + idevice_t device = NULL; + + if (includeNetworkDevices) + { + idevice_new_all(&device, udid); + } + else + { + idevice_new_ignore_network(&device, udid); + } + + if (!device) + { + continue; + } + + lockdownd_client_t client = NULL; + int result = lockdownd_client_new(device, &client, "altserver"); + if (result != LOCKDOWN_E_SUCCESS) + { + fprintf(stderr, "ERROR: Connecting to device %s failed! (%d)\n", udid, result); + + idevice_free(device); + + continue; + } + + char *device_name = NULL; + if (lockdownd_get_device_name(client, &device_name) != LOCKDOWN_E_SUCCESS || device_name == NULL) + { + fprintf(stderr, "ERROR: Could not get device name!\n"); + + lockdownd_client_free(client); + idevice_free(device); + + continue; + } + + plist_t device_type_plist = NULL; + if (lockdownd_get_value(client, NULL, "ProductType", &device_type_plist) != LOCKDOWN_E_SUCCESS) + { + odslog("ERROR: Could not get device type for " << device_name); + + lockdownd_client_free(client); + idevice_free(device); + + continue; + } + + Device::Type deviceType = Device::Type::iPhone; + + char* device_type_string = NULL; + plist_get_string_val(device_type_plist, &device_type_string); + + if (std::string(device_type_string).find("iPhone") != std::string::npos || + std::string(device_type_string).find("iPod") != std::string::npos) + { + deviceType = Device::Type::iPhone; + } + else if (std::string(device_type_string).find("iPad") != std::string::npos) + { + deviceType = Device::Type::iPad; + } + else if (std::string(device_type_string).find("AppleTV") != std::string::npos) + { + deviceType = Device::Type::AppleTV; + } + else + { + odslog("Unknown device type " << device_type_string << " for " << device_name); + deviceType = Device::Type::None; + } + + lockdownd_client_free(client); + idevice_free(device); + + bool isDuplicate = false; + + for (auto& device : availableDevices) + { + if (device->identifier() == udid) + { + // Duplicate. + isDuplicate = true; + break; + } + } + + if (isDuplicate) + { + continue; + } + + auto altDevice = std::make_shared(device_name, udid, deviceType); + availableDevices.push_back(altDevice); + + if (device_name != NULL) + { + free(device_name); + } + } + + idevice_device_list_free(udids); + + return availableDevices; +} + +std::function)> DeviceManager::connectedDeviceCallback() const +{ + return _connectedDeviceCallback; +} + +void DeviceManager::setConnectedDeviceCallback(std::function)> callback) +{ + _connectedDeviceCallback = callback; +} + +std::function)> DeviceManager::disconnectedDeviceCallback() const +{ + return _disconnectedDeviceCallback; +} + +void DeviceManager::setDisconnectedDeviceCallback(std::function)> callback) +{ + _disconnectedDeviceCallback = callback; +} + +std::map>& DeviceManager::cachedDevices() +{ + return _cachedDevices; +} + +#pragma mark - Callbacks - + +void DeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid) +{ + if (DeviceManager::instance()->_installationProgressHandlers.count((char*)uuid) == 0) + { + return; + } + + int percent = 0; + instproxy_status_get_percent_complete(status, &percent); + + char* name = NULL; + char* description = NULL; + uint64_t code = 0; + instproxy_status_get_error(status, &name, &description, &code); + + double progress = ((double)percent / 100.0); + + auto progressHandler = DeviceManager::instance()->_installationProgressHandlers[(char*)uuid]; + progressHandler(progress, code, name, description); +} + +void DeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void* uuid) +{ + char *statusName = NULL; + instproxy_status_get_name(status, &statusName); + + char* errorName = NULL; + char* errorDescription = NULL; + uint64_t errorCode = 0; + instproxy_status_get_error(status, &errorName, &errorDescription, &errorCode); + + if (std::string(statusName) == std::string("Complete") || errorCode != 0 || errorName != NULL) + { + auto completionHandler = DeviceManager::instance()->_deletionCompletionHandlers[(char*)uuid]; + if (completionHandler != NULL) + { + if (errorName == NULL) + { + errorName = (char*)""; + } + + if (errorDescription == NULL) + { + errorDescription = (char*)""; + } + + if (errorCode != 0 || std::string(errorName) != std::string()) + { + odslog("Error removing app. " << errorCode << " (" << errorName << "). " << errorDescription); + completionHandler(false, errorCode, errorName, errorDescription); + } + else + { + odslog("Finished removing app!"); + completionHandler(true, 0, errorName, errorDescription); + } + + DeviceManager::instance()->_deletionCompletionHandlers.erase((char*)uuid); + } + } +} + +void DeviceDidChangeConnectionStatus(const idevice_event_t* event, void* user_data) +{ + switch (event->event) + { + case IDEVICE_DEVICE_ADD: + { + auto devices = DeviceManager::instance()->connectedDevices(); + std::shared_ptr device = NULL; + + for (auto& d : devices) + { + if (d->identifier() == event->udid) + { + device = d; + break; + } + } + + if (device == NULL) + { + return; + } + + if (DeviceManager::instance()->cachedDevices().count(device->identifier()) > 0) + { + return; + } + + odslog("Detected device:" << device->name().c_str()); + + DeviceManager::instance()->cachedDevices()[device->identifier()] = device; + + if (DeviceManager::instance()->connectedDeviceCallback() != NULL) + { + DeviceManager::instance()->connectedDeviceCallback()(device); + } + + break; + } + case IDEVICE_DEVICE_REMOVE: + { + auto devices = DeviceManager::instance()->cachedDevices(); + std::shared_ptr device = DeviceManager::instance()->cachedDevices()[event->udid]; + + if (device == NULL) + { + return; + } + + DeviceManager::instance()->cachedDevices().erase(device->identifier()); + + if (DeviceManager::instance()->disconnectedDeviceCallback() != NULL) + { + DeviceManager::instance()->disconnectedDeviceCallback()(device); + } + + break; + } + default: + break; + } +} diff --git a/src/DeviceManager.hpp b/src/DeviceManager.hpp new file mode 100644 index 0000000..ac27da7 --- /dev/null +++ b/src/DeviceManager.hpp @@ -0,0 +1,89 @@ +// +// DeviceManager.hpp +// AltServer-Windows +// +// Created by Riley Testut on 8/13/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#ifndef DeviceManager_hpp +#define DeviceManager_hpp + +#include "common.h" +#include "Device.hpp" +#include "ProvisioningProfile.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#include "WiredConnection.h" +#include "NotificationConnection.h" + +class DeviceManager +{ +public: + static DeviceManager *instance(); + + DeviceManager(); + + std::vector> connectedDevices() const; + std::vector> availableDevices() const; + + void Start(); + + pplx::task InstallApp(std::string filepath, std::string deviceUDID, std::optional> activeProvisioningProfiles, std::function progressCompletionHandler); + pplx::task RemoveApp(std::string bundleIdentifier, std::string deviceUDID); + + pplx::task> StartWiredConnection(std::shared_ptr device); + pplx::task> StartNotificationConnection(std::shared_ptr device); + + pplx::task InstallProvisioningProfiles(std::vector> profiles, std::string deviceUDID, std::optional> activeProfiles); + pplx::task RemoveProvisioningProfiles(std::set bundleIdentifiers, std::string deviceUDID); + + std::map> RemoveProvisioningProfiles(std::set bundleIdentifiers, misagent_client_t misagent); + std::map> RemoveAllFreeProvisioningProfilesExcludingBundleIdentifiers(std::set excludedBundleIdentifiers, misagent_client_t misagent); + std::map> RemoveAllProvisioningProfiles(std::optional> includedBundleIdentifiers, std::optional> excludedBundleIdentifiers, bool limitedToFreeProfiles, misagent_client_t misagent); + + std::function)> connectedDeviceCallback() const; + void setConnectedDeviceCallback(std::function)> callback); + + std::function)> disconnectedDeviceCallback() const; + void setDisconnectedDeviceCallback(std::function)> callback); + +private: + ~DeviceManager(); + + static DeviceManager *_instance; + + std::mutex _mutex; + + std::map> _installationProgressHandlers; + std::map> _deletionCompletionHandlers; + + std::function)> _connectedDeviceCallback; + std::function)> _disconnectedDeviceCallback; + + std::map> _cachedDevices; + std::map>& cachedDevices(); + + std::vector> availableDevices(bool includeNetworkDevices) const; + + void WriteDirectory(afc_client_t client, std::string directoryPath, std::string destinationPath, std::function wroteFileCallback); + void WriteFile(afc_client_t client, std::string filepath, std::string destinationPath, std::function wroteFileCallback); + + void InstallProvisioningProfile(std::shared_ptr provisioningProfile, misagent_client_t mis); + void RemoveProvisioningProfile(std::shared_ptr provisioningProfile, misagent_client_t mis); + std::vector> CopyProvisioningProfiles(misagent_client_t mis); + + friend void DeviceManagerUpdateStatus(plist_t command, plist_t status, void* uuid); + friend void DeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void* udid); + friend void DeviceDidChangeConnectionStatus(const idevice_event_t* event, void* user_data); +}; + +#endif /* DeviceManager_hpp */ diff --git a/src/InstallError.hpp b/src/InstallError.hpp new file mode 100644 index 0000000..0e3df69 --- /dev/null +++ b/src/InstallError.hpp @@ -0,0 +1,58 @@ +// +// InstallError.h +// AltServer-Windows +// +// Created by Riley Testut on 8/31/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#ifndef InstallError_h +#define InstallError_h + +#include "Error.hpp" + +enum class InstallErrorCode +{ + Cancelled, + NoTeam, + MissingPrivateKey, + MissingCertificate, + MissingInfoPlist, +}; + +class InstallError: public Error +{ +public: + InstallError(InstallErrorCode code) : Error((int)code) + { + } + + virtual std::string domain() const + { + return "com.rileytestut.AltServer.InstallError"; + } + + virtual std::string localizedDescription() const + { + switch ((InstallErrorCode)this->code()) + { + case InstallErrorCode::Cancelled: + return "The operation was cancelled."; + + case InstallErrorCode::NoTeam: + return "You are not a member of any developer teams."; + + case InstallErrorCode::MissingPrivateKey: + return "The developer certificate's private key could not be found."; + + case InstallErrorCode::MissingCertificate: + return "The developer certificate could not be found."; + + case InstallErrorCode::MissingInfoPlist: + return "The app's Info.plist could not be found."; + } + } +}; + + +#endif /* InstallError_h */ diff --git a/src/NotificationConnection.cpp b/src/NotificationConnection.cpp new file mode 100644 index 0000000..42b4b64 --- /dev/null +++ b/src/NotificationConnection.cpp @@ -0,0 +1,102 @@ +#include "common.h" +#include "NotificationConnection.h" +#include "ServerError.hpp" + +#include + +void ALTDeviceReceivedNotification(const char* notification, void* user_data) +{ + NotificationConnection* connection = (NotificationConnection*)user_data; + + if (std::string(notification).size() > 0 && connection->active()) + { + connection->receivedNotificationHandler()(notification); + } +} + +NotificationConnection::NotificationConnection(std::shared_ptr device, np_client_t client) : _device(device), _client(client) +{ + this->setReceivedNotificationHandler([](std::string notification) { + odslog("Received Notification: " << notification); + }); +} + +NotificationConnection::~NotificationConnection() +{ + this->Disconnect(); +} + +void NotificationConnection::Disconnect() +{ + if (_client == NULL) + { + return; + } + + np_client_free(_client); + _client = NULL; + + // Prevent us from receiving callbacks after deallocation. + _active = false; +} + +void NotificationConnection::StartListening(std::vector notifications) +{ + const char** notificationNames = (const char**)malloc((notifications.size() + 1) * sizeof(char *)); + for (int i = 0; i < notifications.size(); i++) + { + const char* cName = notifications[i].c_str(); + notificationNames[i] = cName; + } + notificationNames[notifications.size()] = NULL; // Must have terminating NULL entry. + + np_error_t result = np_observe_notifications(this->client(), notificationNames); + if (result != NP_E_SUCCESS) + { + throw ServerError(ServerErrorCode::LostConnection); + } + + result = np_set_notify_callback(this->client(), ALTDeviceReceivedNotification, this); + if (result != NP_E_SUCCESS) + { + throw ServerError(ServerErrorCode::LostConnection); + } + + _active = true; + + free(notificationNames); +} + +void NotificationConnection::SendNotification(std::string notification) +{ + np_error_t result = np_post_notification(this->client(), notification.c_str()); + if (result != NP_E_SUCCESS) + { + throw ServerError(ServerErrorCode::LostConnection); + } +} + +std::shared_ptr NotificationConnection::device() const +{ + return _device; +} + +np_client_t NotificationConnection::client() const +{ + return _client; +} + +std::function NotificationConnection::receivedNotificationHandler() const +{ + return _receivedNotificationHandler; +} + +void NotificationConnection::setReceivedNotificationHandler(std::function handler) +{ + _receivedNotificationHandler = handler; +} + +bool NotificationConnection::active() const +{ + return _active; +} \ No newline at end of file diff --git a/src/NotificationConnection.h b/src/NotificationConnection.h new file mode 100644 index 0000000..5d1346d --- /dev/null +++ b/src/NotificationConnection.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Device.hpp" + +#include +#include +#include + +#include + +class NotificationConnection +{ +public: + NotificationConnection(std::shared_ptr device, np_client_t client); + ~NotificationConnection(); + + void Disconnect(); + + void StartListening(std::vector notifications); + void SendNotification(std::string notification); + + std::function receivedNotificationHandler() const; + void setReceivedNotificationHandler(std::function handler); + + std::shared_ptr device() const; + + bool active() const; + +private: + std::shared_ptr _device; + np_client_t _client; + std::function _receivedNotificationHandler; + + bool _active; + + np_client_t client() const; +}; + diff --git a/src/PhoneHelper.c b/src/PhoneHelper.c new file mode 100644 index 0000000..610f4c5 --- /dev/null +++ b/src/PhoneHelper.c @@ -0,0 +1,170 @@ +#include "PhoneHelper.h" +#include "phone/global.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/userpref.h" +#include "common/utils.h" + +#ifndef TOOL_NAME +#define TOOL_NAME "AltServerLinux" +#endif +#include "common.h" + +#include "miniwget.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "portlistingparse.h" +#include "upnperrors.h" +struct UPNPUrls _upnpUrls = { 0 }; +struct IGDdatas _upnpData = { 0 }; +char upnpExternalAddr[40] = { 0 }; + +int initUPnP() { + int error = 0; + char lanaddr[64] = "unset"; + int upnpRet = 0; + if (1) { + struct UPNPDev *devlist = upnpDiscover(2000, NULL, "", 0, 0, 2, &error); + upnpRet = UPNP_GetValidIGD(devlist, &_upnpUrls, &_upnpData, lanaddr, sizeof(lanaddr)); + } else { + //upnpRet = 1; UPNP_GetIGDFromUrl("http://192.168.1.1:1900/igd.xml", &_upnpUrls, &_upnpData, lanaddr, sizeof(lanaddr)); + } + upnpUrls = &_upnpUrls; + upnpData = &_upnpData; + if (upnpRet == 1) { + DEBUG_PRINT("Got good upnp igd: %s", _upnpUrls.controlURL); + } else if (upnpRet == 2) { + DEBUG_PRINT("Got not-connected igd: %s", _upnpUrls.controlURL); + } else { + DEBUG_PRINT("Found unknown upnp dev: %s", _upnpUrls.controlURL); + } + if (upnpRet != 1) { + return 0; + } + + char externalIPAddress[40] = { 0 }; + int r = UPNP_GetExternalIPAddress(upnpUrls->controlURL, + upnpData->first.servicetype, + externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) { + DEBUG_PRINT("GetExternalIPAddress failed. (errorcode=%d)\n", r); + return 0; + } + DEBUG_PRINT("Got ExternalIPAddress = %s\n", externalIPAddress); + strcpy(upnpExternalAddr, externalIPAddress); + return 1; +} + +int initGlobalDevice() { + idevice_error_t derr = IDEVICE_E_SUCCESS; + if ((derr = idevice_new_with_options(&g_device, pairUDID, IDEVICE_LOOKUP_NETWORK)) != IDEVICE_E_SUCCESS) { + DEBUG_PRINT("Failed to create device: %d", derr); + return 0; + } + return 1; +} + + +int do_heartbeat(void *_client) { + heartbeat_client_t client = _client; + plist_t ping; + uint64_t interval = 15; + //DEBUG_PRINT("Timer run!"); + if (heartbeat_receive_with_timeout(client, &ping, (uint32_t)interval * 1000) != HEARTBEAT_E_SUCCESS) { + DEBUG_PRINT("Did not recieve ping, canceling timer!"); + return 0; + } + plist_get_uint_val(plist_dict_get_item(ping, "Interval"), &interval); + //DEBUG_PRINT("Set new timer interval: %lu!", interval); + //DEBUG_PRINT("Sending heartbeat."); + heartbeat_send(client, ping); + plist_free(ping); + return (interval); +} + + +int initHeartbeat(void **_hbclient) { + //idevice_set_debug_level(1); + + lockdownd_error_t lerr = LOCKDOWN_E_SUCCESS; + DEBUG_PRINT("Start hb..."); + heartbeat_client_t hbclient; + heartbeat_error_t err = HEARTBEAT_E_UNKNOWN_ERROR; + int herr = heartbeat_client_start_service(g_device, &hbclient, TOOL_NAME); + if (herr != HEARTBEAT_E_SUCCESS) { + DEBUG_PRINT("Failed to create heartbeat service: %d", herr); + return 0; + } + *_hbclient = hbclient; + //idevice_set_debug_level(0); + return 1; +} + + +static void heartbeat_thread(heartbeat_client_t client) { + while (1) { + int interval = do_heartbeat(client); + if (!interval) break; + sleep(interval); + } +} +int startHeartbeat(void *hbclient) { + pthread_t thHeartbeat; + //pthread_create(&thHeartbeat, NULL, (void * (*)(void *))heartbeat_thread, hbclient); + int ret = fork(); + if (ret == 0) { + heartbeat_thread(hbclient); + } +} + +void setupPairInfo(const char *udid, const char *ipaddr, const char *pairDataFile) { + DEBUG_PRINT("Setup pairInfo..."); + strcpy(pairUDID, udid); + strcpy(pairDeviceAddress, ipaddr); + FILE *f = fopen(pairDataFile, "rb"); + fseek(f, 0, SEEK_END); + pairDataLen = ftell(f); + fseek(f, 0, SEEK_SET); + fread(pairData, 1, pairDataLen, f); +} + +// int main(int argc, char *argv[]) { +// setvbuf(stdin, NULL, _IONBF, 0); +// setvbuf(stdout, NULL, _IONBF, 0); +// setvbuf(stderr, NULL, _IONBF, 0); + +// srand(time(NULL)); +// DEBUG_PRINT("Setup pairInfo..."); +// strcpy(pairUDID, argv[1]); +// strcpy(pairDeviceAddress, argv[2]); +// FILE *f = fopen(argv[3], "rb"); +// fseek(f, 0, SEEK_END); +// pairDataLen = ftell(f); +// fseek(f, 0, SEEK_SET); +// fread(pairData, 1, pairDataLen, f); + +// if (!initUPnP()) { +// DEBUG_PRINT("failed to init upnp! exitting..."); +// return 1; +// } +// DEBUG_PRINT("upnp init successfully!"); + +// DEBUG_PRINT("Connect device..."); +// if (!initHeartbeat()) { +// DEBUG_PRINT("failed to init heartbeat! exitting..."); +// return 1; +// } + +// // DEBUG_PRINT("Start tool..."); +// // tool_main(argc - 3, argv + 3); +// } diff --git a/src/PhoneHelper.h b/src/PhoneHelper.h new file mode 100644 index 0000000..683b1f2 --- /dev/null +++ b/src/PhoneHelper.h @@ -0,0 +1,11 @@ +#ifdef __cplusplus +extern "C" { +#endif + int initGlobalDevice(); + int initHeartbeat(void **hbclient); + int do_heartbeat(void *_client); + int initUPnP(); + void setupPairInfo(const char *udid, const char *ipaddr, const char *pairDataFile); +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/Semaphore.h b/src/Semaphore.h new file mode 100644 index 0000000..9f6b7da --- /dev/null +++ b/src/Semaphore.h @@ -0,0 +1,40 @@ +// +// Semaphore.hpp +// AltServer-Windows +// +// Created by Riley Testut on 10/7/20. +// Copied from https://stackoverflow.com/a/19659736 +// + +#pragma once + +#include +#include + +class Semaphore { +public: + Semaphore(int count_ = 0) + : count(count_) {} + + inline void notify() + { + std::unique_lock lock(mtx); + count++; + cv.notify_one(); + } + + inline void wait() + { + std::unique_lock lock(mtx); + + while (count <= 0) { + cv.wait(lock); + } + count--; + } + +private: + std::mutex mtx; + std::condition_variable cv; + int count; +}; \ No newline at end of file diff --git a/src/ServerError.cpp b/src/ServerError.cpp new file mode 100644 index 0000000..6d1a127 --- /dev/null +++ b/src/ServerError.cpp @@ -0,0 +1,5 @@ +#include "ServerError.hpp" + +std::string LocalizedFailureErrorKey = "NSLocalizedFailure"; +std::string UnderlyingErrorCodeErrorKey = "underlyingErrorCode"; +std::string ProvisioningProfileBundleIDErrorKey = "bundleIdentifier"; \ No newline at end of file diff --git a/src/ServerError.hpp b/src/ServerError.hpp new file mode 100644 index 0000000..a154d88 --- /dev/null +++ b/src/ServerError.hpp @@ -0,0 +1,123 @@ +// +// ServerError.hpp +// AltServer-Windows +// +// Created by Riley Testut on 8/13/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#ifndef ServerError_hpp +#define ServerError_hpp + +#include "Error.hpp" + +extern std::string LocalizedFailureErrorKey; +extern std::string UnderlyingErrorCodeErrorKey; +extern std::string ProvisioningProfileBundleIDErrorKey; + +enum class ServerErrorCode +{ + UnderlyingError = -1, + + Unknown = 0, + ConnectionFailed = 1, + LostConnection = 2, + + DeviceNotFound = 3, + DeviceWriteFailed = 4, + + InvalidRequest = 5, + InvalidResponse = 6, + + InvalidApp = 7, + InstallationFailed = 8, + MaximumFreeAppLimitReached = 9, + UnsupportediOSVersion = 10, + + UnknownRequest = 11, + UnknownResponse = 12, + + InvalidAnisetteData = 13, + PluginNotFound = 14, + + ProfileNotFound = 15, + + AppDeletionFailed = 16 +}; + +class ServerError: public Error +{ +public: + ServerError(ServerErrorCode code) : Error((int)code) + { + } + + ServerError(ServerErrorCode code, std::map userInfo) : Error((int)code, userInfo) + { + } + + virtual std::string domain() const + { + return "com.rileytestut.AltServer"; + } + + virtual std::string localizedDescription() const + { + switch ((ServerErrorCode)this->code()) + { + case ServerErrorCode::UnderlyingError: + case ServerErrorCode::Unknown: + return "An unknown error occured."; + + case ServerErrorCode::ConnectionFailed: + return "Could not connect to device."; + + case ServerErrorCode::LostConnection: + return "Lost connection to AltServer."; + + case ServerErrorCode::DeviceNotFound: + return "AltServer could not find the device."; + + case ServerErrorCode::DeviceWriteFailed: + return "Failed to write app data to device."; + + case ServerErrorCode::InvalidRequest: + return "AltServer received an invalid request."; + + case ServerErrorCode::InvalidResponse: + return "AltServer sent an invalid response."; + + case ServerErrorCode::InvalidApp: + return "The app is invalid."; + + case ServerErrorCode::InstallationFailed: + return "An error occured while installing the app."; + + case ServerErrorCode::MaximumFreeAppLimitReached: + return "You have reached the limit of 3 apps per device.\n\nIf you're running iOS 13.5 or later, make sure 'Offload Unused Apps' is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps to prevent them from erroneously counting towards this limit."; + + case ServerErrorCode::UnsupportediOSVersion: + return "Your device must be running iOS 12.2 or later to install AltStore."; + + case ServerErrorCode::UnknownRequest: + return "AltServer does not support this request."; + + case ServerErrorCode::UnknownResponse: + return "Received an unknown response from AltServer."; + + case ServerErrorCode::InvalidAnisetteData: + return "Invalid anisette data. Please download the latest versions of iTunes and iCloud directly from Apple, and not from the Microsoft Store."; + + case ServerErrorCode::PluginNotFound: + return "Could not connect to Mail plug-in. Please make sure the plug-in is installed and Mail is running, then try again."; + + case ServerErrorCode::ProfileNotFound: + return "Could not find provisioning profile."; + + case ServerErrorCode::AppDeletionFailed: + return "An error occured while removing the app."; + } + } +}; + +#endif /* ServerError_hpp */ diff --git a/src/WiredConnection.cpp b/src/WiredConnection.cpp new file mode 100644 index 0000000..825d19b --- /dev/null +++ b/src/WiredConnection.cpp @@ -0,0 +1,73 @@ +#include "WiredConnection.h" +#include "ServerError.hpp" + +WiredConnection::WiredConnection(std::shared_ptr device, idevice_connection_t connection) : _device(device), _connection(connection) +{ +} + +WiredConnection::~WiredConnection() +{ +} + +void WiredConnection::Disconnect() +{ + if (this->connection() == NULL) + { + return; + } + + idevice_disconnect(this->connection()); + _connection = NULL; +} + +pplx::task WiredConnection::SendData(std::vector& data) +{ + return pplx::create_task([&data, this]() { + while (data.size() > 0) + { + uint32_t sentBytes = 0; + if (idevice_connection_send(this->connection(), (const char*)data.data(), (int32_t)data.size(), &sentBytes) != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::LostConnection); + } + + data.erase(data.begin(), data.begin() + sentBytes); + } + }); +} + +pplx::task> WiredConnection::ReceiveData(int expectedSize) +{ + return pplx::create_task([=]() -> std::vector { + char bytes[4096]; + + std::vector receivedData; + receivedData.reserve(expectedSize); + + while (receivedData.size() < expectedSize) + { + uint32_t size = std::min((uint32_t)4096, (uint32_t)expectedSize - (uint32_t)receivedData.size()); + + uint32_t receivedBytes = 0; + idevice_error_t result = idevice_connection_receive_timeout(this->connection(), bytes, size, &receivedBytes, 0); + if (result != IDEVICE_E_SUCCESS) + { + throw ServerError(ServerErrorCode::LostConnection); + } + + receivedData.insert(receivedData.end(), bytes, bytes + receivedBytes); + } + + return receivedData; + }); +} + +std::shared_ptr WiredConnection::device() const +{ + return _device; +} + +idevice_connection_t WiredConnection::connection() const +{ + return _connection; +} diff --git a/src/WiredConnection.h b/src/WiredConnection.h new file mode 100644 index 0000000..57ddcca --- /dev/null +++ b/src/WiredConnection.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Device.hpp" +#include "ClientConnection.h" + +#include +#include + +#include + +class WiredConnection: public ClientConnection +{ +public: + WiredConnection(std::shared_ptr device, idevice_connection_t connection); + virtual ~WiredConnection(); + + virtual void Disconnect(); + + virtual pplx::task SendData(std::vector& data); + virtual pplx::task> ReceiveData(int expectedSize); + + std::shared_ptr device() const; + +private: + std::shared_ptr _device; + idevice_connection_t _connection; + + idevice_connection_t connection() const; +}; + diff --git a/src/WirelessConnection.cpp b/src/WirelessConnection.cpp new file mode 100644 index 0000000..deb61dc --- /dev/null +++ b/src/WirelessConnection.cpp @@ -0,0 +1,137 @@ +#include "WirelessConnection.h" +#include +using std::min; +#include +#include + +#include "ServerError.hpp" + +#if SIZE_MAX == UINT_MAX +typedef int ssize_t; /* common 32 bit case */ +#elif SIZE_MAX == ULONG_MAX +typedef long ssize_t; /* linux 64 bits */ +#elif SIZE_MAX == ULLONG_MAX +typedef long long ssize_t; /* windows 64 bits */ +#elif SIZE_MAX == USHRT_MAX +typedef short ssize_t; /* is this even possible? */ +#else +#error platform has exotic SIZE_MAX +#endif + +WirelessConnection::WirelessConnection(int socket) : _socket(socket) +{ +} + +WirelessConnection::~WirelessConnection() +{ +} + +void WirelessConnection::Disconnect() +{ + if (this->socket() == 0) + { + return; + } + + closesocket(this->socket()); + _socket = 0; +} + +pplx::task WirelessConnection::SendData(std::vector& data) +{ + return pplx::create_task([this, data]() { + fd_set input_set; + fd_set copy_set; + + int64_t totalSentBytes = 0; + + while (true) + { + struct timeval tv; + tv.tv_sec = 1; /* 1 second timeout */ + tv.tv_usec = 0; /* no microseconds. */ + + /* Selection */ + FD_ZERO(&input_set); /* Empty the FD Set */ + FD_SET(this->socket(), &input_set); /* Listen to the input descriptor */ + + FD_ZERO(©_set); /* Empty the FD Set */ + FD_SET(this->socket(), ©_set); /* Listen to the input descriptor */ + + ssize_t sentBytes = send(this->socket(), (const char*)data.data(), (size_t)(data.size() - totalSentBytes), 0); + totalSentBytes += sentBytes; + + std::cout << "Sent Bytes Count: " << sentBytes << " (" << totalSentBytes << ")" << std::endl; + + if (totalSentBytes >= sentBytes) + { + break; + } + } + + std::cout << "Sent Data: " << totalSentBytes << " Bytes" << std::endl; + }); +} + +pplx::task> WirelessConnection::ReceiveData(int size) +{ + return pplx::create_task([this, size]() { + std::vector data; + data.reserve(size); + + char buffer[4096]; + + fd_set input_set; + fd_set copy_set; + + while (true) + { + struct timeval tv; + tv.tv_sec = 1; /* 1 second timeout */ + tv.tv_usec = 0; /* no microseconds. */ + + int socket = this->socket(); + std::cout << "Checking socket: " << socket << std::endl; + + /* Selection */ + FD_ZERO(&input_set); /* Empty the FD Set */ + FD_SET(socket, &input_set); /* Listen to the input descriptor */ + + FD_ZERO(©_set); /* Empty the FD Set */ + FD_SET(socket, ©_set); /* Listen to the input descriptor */ + + int result = select(this->socket() + 1, &input_set, ©_set, NULL, &tv); + + if (result == 0) + { + continue; + } + else if (result == -1) + { + std::cout << "Error!" << std::endl; + } + else + { + ssize_t readBytes = recv(this->socket(), buffer, min((ssize_t)4096, (ssize_t)(size - data.size())), 0); + for (int i = 0; i < readBytes; i++) + { + data.push_back(buffer[i]); + } + + odslog("Received bytes: " << data.size() << "(of " << size << ")"); + + if (data.size() >= size) + { + break; + } + } + } + + return data; + }); +} + +int WirelessConnection::socket() const +{ + return _socket; +} \ No newline at end of file diff --git a/src/WirelessConnection.h b/src/WirelessConnection.h new file mode 100644 index 0000000..3b3f470 --- /dev/null +++ b/src/WirelessConnection.h @@ -0,0 +1,21 @@ +#pragma once + +#include "ClientConnection.h" + +class WirelessConnection: public ClientConnection +{ +public: + WirelessConnection(int socket); + virtual ~WirelessConnection(); + + virtual void Disconnect(); + + virtual pplx::task SendData(std::vector& data); + virtual pplx::task> ReceiveData(int size); + + int socket() const; + +private: + int _socket; +}; + diff --git a/src/ideviceinstaller-wrap.c b/src/bak/ideviceinstaller-wrap._c similarity index 100% rename from src/ideviceinstaller-wrap.c rename to src/bak/ideviceinstaller-wrap._c diff --git a/src/lockdown-stub._c b/src/bak/lockdown-stub._c similarity index 100% rename from src/lockdown-stub._c rename to src/bak/lockdown-stub._c diff --git a/src/common.h b/src/common.h index 6bfccf9..685ec85 100644 --- a/src/common.h +++ b/src/common.h @@ -1,14 +1,9 @@ -#include -#include +#pragma once -char pairUDID[256]; -char pairDeviceAddress[256]; -char pairData[16384]; -uint32_t pairDataLen; +#include -struct UPNPUrls *upnpUrls; -struct IGDdatas *upnpData; -char upnpExternalAddr[40]; +typedef int HWND; +typedef int HINSTANCE; #define DEBUG_PRINT(...) do { \ fprintf(stderr, "[%s:%d] ", __FUNCTION__, __LINE__); \ @@ -16,4 +11,19 @@ char upnpExternalAddr[40]; fprintf(stderr, "\n"); \ } while (0) -idevice_t g_device; \ No newline at end of file +#include +#include + +#include +#include +#define closesocket close +typedef struct timeval TIMEVAL; +typedef int BOOL; + +#ifndef odslog +#define odslog(msg) { std::cout << msg << std::endl; } +#endif + + +#define OutputDebugStringW(x) (std::wcout << x) +#define OutputDebugStringA(x) (std::cout << x) \ No newline at end of file diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 71f12c6..0000000 --- a/src/main.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "common.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/userpref.h" -#include "common/utils.h" - -#ifndef TOOL_NAME -#define TOOL_NAME "jitterbug1" -#endif - -static void heartbeat_thread(heartbeat_client_t client) { - while (1) { - plist_t ping; - uint64_t interval = 15; - //DEBUG_PRINT("Timer run!"); - if (heartbeat_receive_with_timeout(client, &ping, (uint32_t)interval * 1000) != HEARTBEAT_E_SUCCESS) { - DEBUG_PRINT("Did not recieve ping, canceling timer!"); - return; - } - plist_get_uint_val(plist_dict_get_item(ping, "Interval"), &interval); - //DEBUG_PRINT("Set new timer interval: %lu!", interval); - //DEBUG_PRINT("Sending heartbeat."); - heartbeat_send(client, ping); - plist_free(ping); - sleep(interval); - } -} - - -#include "miniwget.h" -#include "miniupnpc.h" -#include "upnpcommands.h" -#include "portlistingparse.h" -#include "upnperrors.h" -struct UPNPUrls _upnpUrls = { 0 }; -struct IGDdatas _upnpData = { 0 }; -char upnpExternalAddr[40] = { 0 }; -int initUPnP() { - int error = 0; - char lanaddr[64] = "unset"; - int upnpRet = 0; - //struct UPNPDev *devlist = upnpDiscover(2000, NULL, "", 0, 0, 2, &error); - //upnpRet = UPNP_GetValidIGD(devlist, &_upnpUrls, &_upnpData, lanaddr, sizeof(lanaddr)); - upnpRet = 1; UPNP_GetIGDFromUrl("http://192.168.1.1:1900/igd.xml", &_upnpUrls, &_upnpData, lanaddr, sizeof(lanaddr)); - upnpUrls = &_upnpUrls; - upnpData = &_upnpData; - if (upnpRet == 1) { - DEBUG_PRINT("Got good upnp igd: %s", _upnpUrls.controlURL); - } else if (upnpRet == 2) { - DEBUG_PRINT("Got not-connected igd: %s", _upnpUrls.controlURL); - } else { - DEBUG_PRINT("Found unknown upnp dev: %s", _upnpUrls.controlURL); - } - if (upnpRet != 1) { - return 0; - } - - char externalIPAddress[40] = { 0 }; - int r = UPNP_GetExternalIPAddress(upnpUrls->controlURL, - upnpData->first.servicetype, - externalIPAddress); - if(r != UPNPCOMMAND_SUCCESS) { - DEBUG_PRINT("GetExternalIPAddress failed. (errorcode=%d)\n", r); - return 0; - } - DEBUG_PRINT("Got ExternalIPAddress = %s\n", externalIPAddress); - strcpy(upnpExternalAddr, externalIPAddress); - return 1; -} - -int tool_main(int argc, char *argv[]); - -int main(int argc, char *argv[]) { - setvbuf(stdin, NULL, _IONBF, 0); - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - - srand(time(NULL)); - DEBUG_PRINT("Setup pairInfo..."); - strcpy(pairUDID, argv[1]); - strcpy(pairDeviceAddress, argv[2]); - FILE *f = fopen(argv[3], "rb"); - fseek(f, 0, SEEK_END); - pairDataLen = ftell(f); - fseek(f, 0, SEEK_SET); - fread(pairData, 1, pairDataLen, f); - - if (!initUPnP()) { - DEBUG_PRINT("failed to init upnp! exitting..."); - return 1; - } - DEBUG_PRINT("upnp init successfully!"); - - idevice_set_debug_level(1); - DEBUG_PRINT("Connect device..."); - idevice_error_t derr = IDEVICE_E_SUCCESS; - lockdownd_error_t lerr = LOCKDOWN_E_SUCCESS; - if ((derr = idevice_new_with_options(&g_device, pairUDID, IDEVICE_LOOKUP_NETWORK)) != IDEVICE_E_SUCCESS) { - DEBUG_PRINT("Failed to create device: %d", derr); - return 1; - } - - DEBUG_PRINT("Start hb..."); - heartbeat_client_t hbclient; - heartbeat_error_t err = HEARTBEAT_E_UNKNOWN_ERROR; - err = heartbeat_client_start_service(g_device, &hbclient, TOOL_NAME); - idevice_set_debug_level(0); - - pthread_t thHeartbeat; - //pthread_create(&thHeartbeat, NULL, (void * (*)(void *))heartbeat_thread, hbclient); - int ret = fork(); - if (ret == 0) { - heartbeat_thread(hbclient); - } - - DEBUG_PRINT("Start tool..."); - tool_main(argc - 3, argv + 3); -} diff --git a/src/muslfix.c b/src/muslfix.c new file mode 100644 index 0000000..c9c6b7f --- /dev/null +++ b/src/muslfix.c @@ -0,0 +1,19 @@ +#include +#include +void * +__memcpy_chk (void *dest, const void *src, size_t len, size_t dstlen) +{ + return memcpy (dest, src, len); +} + +void * +__memset_chk (void *dest, int val, size_t len, size_t dstlen) +{ + return memset (dest, val, len); +} + +void * +__memmove_chk (void *dest, const void *src, size_t len, size_t dstlen) +{ + return memmove (dest, src, len); +} \ No newline at end of file diff --git a/src/phone/global.h b/src/phone/global.h new file mode 100644 index 0000000..75f59b5 --- /dev/null +++ b/src/phone/global.h @@ -0,0 +1,19 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +char pairUDID[256]; +char pairDeviceAddress[256]; +char pairData[16384]; +uint32_t pairDataLen; + +struct UPNPUrls *upnpUrls; +struct IGDdatas *upnpData; +char upnpExternalAddr[40]; +idevice_t g_device; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/idevice-stub.c b/src/phone/idevice-stub.c similarity index 98% rename from src/idevice-stub.c rename to src/phone/idevice-stub.c index 1bc738b..3159b76 100644 --- a/src/idevice-stub.c +++ b/src/phone/idevice-stub.c @@ -1,4 +1,6 @@ -#include "common.h" +#include "../common.h" +#include "global.h" + #define idevice_connect wrap_idevice_connect #include "../libraries/libimobiledevice/src/idevice.c" #undef idevice_connect diff --git a/src/libusbmuxd-stub.c b/src/phone/libusbmuxd-stub.c similarity index 99% rename from src/libusbmuxd-stub.c rename to src/phone/libusbmuxd-stub.c index 5c4a464..4523df0 100644 --- a/src/libusbmuxd-stub.c +++ b/src/phone/libusbmuxd-stub.c @@ -35,15 +35,14 @@ //#include "CacheStorage.h" //#include "Jitterbug.h" -#include "common.h" +#include "../common.h" +#include "global.h" char pairUDID[256] = { 0 }; char pairDeviceAddress[256] = { 0 }; char pairData[16384] = { 0 }; uint32_t pairDataLen = 0; - - #pragma mark - Device listing struct sockaddr_in_darwin {