| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/updater/mac/keystone/ksadmin.h" |
| |
| #include <stdio.h> |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/time/time.h" |
| #include "chrome/updater/app/app.h" |
| #include "chrome/updater/constants.h" |
| #include "chrome/updater/ipc/ipc_support.h" |
| #include "chrome/updater/mac/setup/ks_tickets.h" |
| #include "chrome/updater/registration_data.h" |
| #include "chrome/updater/service_proxy_factory.h" |
| #include "chrome/updater/update_service.h" |
| #include "chrome/updater/updater_scope.h" |
| #include "chrome/updater/updater_version.h" |
| #include "chrome/updater/util/mac_util.h" |
| #include "chrome/updater/util/util.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace updater { |
| |
| namespace { |
| |
| std::string ReadPlist(const std::string& path, const std::string& key) { |
| return base::SysNSStringToUTF8([NSDictionary |
| dictionaryWithContentsOfFile:base::SysUTF8ToNSString( |
| path)][base::SysUTF8ToNSString(key)]); |
| } |
| |
| } // namespace |
| |
| // base::CommandLine can't be used because it enforces that all switches are |
| // lowercase, but ksadmin has case-sensitive switches. This argument parser |
| // converts an argv set into a map of switch name to switch value; for example |
| // `ksadmin --register --productid com.goog.chrome -v 1.2.3.4 e` to |
| // `{"register": "", "productid": "com.goog.chrome", "v": "1.2.3.4", "e": ""}`. |
| std::map<std::string, std::string> ParseCommandLine(int argc, |
| const char* argv[]) { |
| std::map<std::string, std::string> result; |
| std::string key; |
| for (int i = 1; i < argc; ++i) { |
| std::string arg(argv[i]); |
| if (base::StartsWith(arg, "--")) { |
| key = arg.substr(2); |
| result[key] = ""; |
| } else if (base::StartsWith(arg, "-")) { |
| // Multiple short options could be combined together. For example, |
| // command `ksadmin -pP com.google.Chrome` should print Chrome ticket. |
| // Split the option substring into switches character by character. |
| for (const char ch : arg.substr(1)) { |
| key = ch; |
| result[key] = ""; |
| } |
| } else { |
| if (!key.empty()) |
| result[key] = arg; |
| key = ""; |
| } |
| } |
| return result; |
| } |
| |
| namespace { |
| |
| constexpr char kCommandDelete[] = "delete"; |
| constexpr char kCommandInstall[] = "install"; |
| constexpr char kCommandList[] = "list"; |
| constexpr char kCommandKsadminVersion[] = "ksadmin-version"; |
| constexpr char kCommandPrintTag[] = "print-tag"; |
| constexpr char kCommandPrintTickets[] = "print-tickets"; |
| constexpr char kCommandRegister[] = "register"; |
| constexpr char kCommandSystemStore[] = "system-store"; |
| constexpr char kCommandUserInitiated[] = "user-initiated"; |
| constexpr char kCommandUserStore[] = "user-store"; |
| constexpr char kCommandStorePath[] = "store"; |
| constexpr char kCommandBrandKey[] = "brand-key"; |
| constexpr char kCommandBrandPath[] = "brand-path"; |
| constexpr char kCommandProductId[] = "productid"; |
| constexpr char kCommandTag[] = "tag"; |
| constexpr char kCommandTagKey[] = "tag-key"; |
| constexpr char kCommandTagPath[] = "tag-path"; |
| constexpr char kCommandVersion[] = "version"; |
| constexpr char kCommandVersionKey[] = "version-key"; |
| constexpr char kCommandVersionPath[] = "version-path"; |
| constexpr char kCommandXCPath[] = "xcpath"; |
| |
| bool HasSwitch(const std::string& arg, |
| const std::map<std::string, std::string>& switches) { |
| if (base::Contains(switches, arg)) |
| return true; |
| static const base::NoDestructor< |
| std::map<std::string, std::vector<std::string>>> |
| aliases{{ |
| {kCommandDelete, {"d"}}, |
| {kCommandInstall, {"i"}}, |
| {kCommandList, {"l"}}, |
| {kCommandKsadminVersion, {"k"}}, |
| {kCommandPrintTag, {"G"}}, |
| {kCommandPrintTickets, {"print", "p"}}, |
| {kCommandRegister, {"r"}}, |
| {kCommandSystemStore, {"S"}}, |
| {kCommandUserInitiated, {"F"}}, |
| {kCommandUserStore, {"U"}}, |
| }}; |
| if (!base::Contains(*aliases, arg)) |
| return false; |
| for (const auto& alias : aliases->at(arg)) { |
| if (base::Contains(switches, alias)) |
| return true; |
| } |
| return false; |
| } |
| |
| std::string SwitchValue(const std::string& arg, |
| const std::map<std::string, std::string>& switches) { |
| if (base::Contains(switches, arg)) |
| return switches.at(arg); |
| static const base::NoDestructor<std::map<std::string, std::string>> aliases{{ |
| {kCommandBrandKey, "b"}, |
| {kCommandBrandPath, "B"}, |
| {kCommandProductId, "P"}, |
| {kCommandTag, "g"}, |
| {kCommandTagKey, "K"}, |
| {kCommandTagPath, "H"}, |
| {kCommandVersion, "v"}, |
| {kCommandVersionKey, "e"}, |
| {kCommandVersionPath, "a"}, |
| {kCommandXCPath, "x"}, |
| }}; |
| if (!base::Contains(*aliases, arg)) |
| return ""; |
| const std::string& alias = aliases->at(arg); |
| return base::Contains(switches, alias) ? switches.at(alias) : ""; |
| } |
| |
| std::string KeystoneTicketStorePath(UpdaterScope scope) { |
| return GetKeystoneFolderPath(scope) |
| ->Append(FILE_PATH_LITERAL("TicketStore")) |
| .Append(FILE_PATH_LITERAL("Keystone.ticketstore")) |
| .value(); |
| } |
| |
| bool IsSystemShim() { |
| base::FilePath executable_path; |
| if (!base::PathService::Get(base::FILE_EXE, &executable_path)) |
| return false; |
| |
| return base::StartsWith( |
| executable_path.value(), |
| GetKeystoneFolderPath(UpdaterScope::kSystem)->value()); |
| } |
| |
| UpdaterScope Scope(const std::map<std::string, std::string>& switches) { |
| if (HasSwitch(kCommandSystemStore, switches)) |
| return UpdaterScope::kSystem; |
| if (HasSwitch(kCommandUserStore, switches)) |
| return UpdaterScope::kUser; |
| |
| if (HasSwitch(kCommandStorePath, switches)) { |
| return SwitchValue(kCommandStorePath, switches) == |
| KeystoneTicketStorePath(UpdaterScope::kSystem) |
| ? UpdaterScope::kSystem |
| : UpdaterScope::kUser; |
| } |
| return IsSystemShim() ? UpdaterScope::kSystem : UpdaterScope::kUser; |
| } |
| |
| void MaybeInstallUpdater(UpdaterScope scope) { |
| const absl::optional<base::FilePath> path = GetUpdaterExecutablePath(scope); |
| |
| if (path && |
| [NSFileManager.defaultManager |
| fileExistsAtPath:base::apple::FilePathToNSString(path.value())]) { |
| // Updater is already installed. |
| return; |
| } |
| |
| if (IsSystemInstall(scope) && geteuid() != 0) { |
| VLOG(0) << "Cannot install system updater without root privilege."; |
| return; |
| } |
| |
| const absl::optional<base::FilePath> setup_path = GetUpdaterExecutablePath( |
| IsSystemShim() ? UpdaterScope::kSystem : UpdaterScope::kUser); |
| if (!setup_path || ![NSFileManager.defaultManager |
| fileExistsAtPath:base::apple::FilePathToNSString( |
| setup_path.value())]) { |
| return; |
| } |
| |
| base::CommandLine install_command(setup_path.value()); |
| install_command.AppendSwitch(kInstallSwitch); |
| install_command.AppendSwitch(kEnableLoggingSwitch); |
| install_command.AppendSwitchASCII(kLoggingModuleSwitch, |
| kLoggingModuleSwitchValue); |
| if (IsSystemInstall(scope)) { |
| install_command.AppendSwitch(kSystemSwitch); |
| } |
| int exit_code = -1; |
| if (base::LaunchProcess(install_command, {}).WaitForExit(&exit_code)) { |
| VLOG(0) << "Installer returned " << exit_code << "."; |
| } else { |
| VLOG(0) << "Failed to wait for the installer to exit."; |
| } |
| } |
| |
| class UpdateCheckResult : public base::RefCountedThreadSafe<UpdateCheckResult> { |
| public: |
| explicit UpdateCheckResult(const std::string& app_id) : app_id_(app_id) {} |
| |
| std::string app_id() const { return app_id_; } |
| std::string next_version() const { return next_version_; } |
| |
| void set_next_version(const std::string& next_version) { |
| next_version_ = next_version; |
| } |
| |
| protected: |
| virtual ~UpdateCheckResult() = default; |
| |
| private: |
| friend class base::RefCountedThreadSafe<UpdateCheckResult>; |
| |
| const std::string app_id_; |
| |
| // The version that the app can currently upgrade to. |
| std::string next_version_; |
| }; |
| |
| class KSAdminApp : public App { |
| public: |
| explicit KSAdminApp(const std::map<std::string, std::string>& switches) |
| : switches_(switches), |
| system_service_proxy_(CreateUpdateServiceProxy(UpdaterScope::kSystem)), |
| user_service_proxy_(CreateUpdateServiceProxy(UpdaterScope::kUser)) {} |
| |
| private: |
| ~KSAdminApp() override = default; |
| void FirstTaskRun() override; |
| |
| // Command handlers; each will eventually call Shutdown. |
| void UpdateApp(); |
| void ListAppUpdate(); |
| void Register(); |
| void Delete(); |
| void PrintTag(); |
| void PrintUsage(const std::string& error_message); |
| void PrintVersion(); |
| void PrintTickets(); |
| |
| void DoUpdateApp(UpdaterScope scope); |
| void DoListAppUpdate(UpdaterScope scope); |
| void DoPrintTag(UpdaterScope scope); |
| void DoPrintTickets(UpdaterScope scope); |
| |
| static KSTicket* TicketFromAppState( |
| const updater::UpdateService::AppState& state); |
| int PrintKeystoneTag(const std::string& app_id) const; |
| bool PrintKeystoneTickets(const std::string& app_id) const; |
| |
| scoped_refptr<UpdateService> ServiceProxy(UpdaterScope scope) const; |
| void ChooseService( |
| base::OnceCallback<void(UpdaterScope scope)> callback) const; |
| |
| bool HasSwitch(const std::string& arg) const; |
| std::string SwitchValue(const std::string& arg) const; |
| |
| NSDictionary<NSString*, KSTicket*>* LoadTicketStore() const; |
| |
| const std::map<std::string, std::string> switches_; |
| scoped_refptr<UpdateService> system_service_proxy_; |
| scoped_refptr<UpdateService> user_service_proxy_; |
| ScopedIPCSupportWrapper ipc_support_; |
| }; |
| |
| KSTicket* KSAdminApp::TicketFromAppState( |
| const updater::UpdateService::AppState& state) { |
| return [[KSTicket alloc] |
| initWithAppId:base::SysUTF8ToNSString(state.app_id) |
| version:base::SysUTF8ToNSString(state.version.GetString()) |
| ecp:state.ecp |
| tag:base::SysUTF8ToNSString(state.ap) |
| brandCode:base::SysUTF8ToNSString(state.brand_code) |
| brandPath:state.brand_path]; |
| } |
| |
| scoped_refptr<UpdateService> KSAdminApp::ServiceProxy( |
| UpdaterScope scope) const { |
| return IsSystemInstall(scope) ? system_service_proxy_ : user_service_proxy_; |
| } |
| |
| void KSAdminApp::ChooseService( |
| base::OnceCallback<void(UpdaterScope)> callback) const { |
| // Choose updater in the following order: |
| // 1. If user explicitly specified the scope (based on `-S` or `-U` or |
| // value of `--store`). |
| // 2. Choose user scope if shim is user scope. |
| // 3. Choose system updater if user is root. |
| // 4. Prefer system updater if app ID is given and is a system app. |
| // 5. Otherwise choose user updater. |
| absl::optional<UpdaterScope> scope = absl::nullopt; |
| if (HasSwitch(kCommandSystemStore)) { |
| scope = absl::make_optional(UpdaterScope::kSystem); |
| } else if (HasSwitch(kCommandUserStore) || !IsSystemShim()) { |
| scope = absl::make_optional(UpdaterScope::kUser); |
| } else if (HasSwitch(kCommandStorePath)) { |
| scope = absl::make_optional( |
| SwitchValue(kCommandStorePath) == |
| KeystoneTicketStorePath(UpdaterScope::kSystem) |
| ? UpdaterScope::kSystem |
| : UpdaterScope::kUser); |
| } else if (geteuid() == 0) { |
| scope = absl::make_optional(UpdaterScope::kSystem); |
| } else { |
| const std::string app_id = SwitchValue(kCommandProductId); |
| if (app_id.empty()) |
| scope = absl::make_optional(UpdaterScope::kSystem); |
| } |
| |
| if (scope) { |
| MaybeInstallUpdater(scope.value()); |
| std::move(callback).Run(scope.value()); |
| } else { |
| system_service_proxy_->GetAppStates(base::BindOnce( |
| [](const std::string& app_id, |
| base::OnceCallback<void(UpdaterScope scope)> callback, |
| const std::vector<updater::UpdateService::AppState>& states) { |
| for (const updater::UpdateService::AppState& state : states) { |
| if (base::EqualsCaseInsensitiveASCII(app_id, state.app_id)) { |
| return std::move(callback).Run(UpdaterScope::kSystem); |
| } |
| } |
| |
| MaybeInstallUpdater(UpdaterScope::kUser); |
| std::move(callback).Run(UpdaterScope::kUser); |
| }, |
| SwitchValue(kCommandProductId), std::move(callback))); |
| } |
| } |
| |
| void KSAdminApp::PrintUsage(const std::string& error_message) { |
| if (!error_message.empty()) |
| LOG(ERROR) << error_message; |
| const std::string usage_message = |
| "Usage: ksadmin [action...] [option...]\n" |
| "Actions:\n" |
| " --delete,-d Delete a ticket. Use with -P.\n" |
| " --install,-i Check for and apply updates.\n" |
| " --ksadmin-version,-k Print the version of ksadmin.\n" |
| " --print An alias for --print-tickets.\n" |
| " --print-tag,-G Print a ticket's tag. Use with -P.\n" |
| " --print-tickets,-p Print all tickets. Can filter with -P.\n" |
| " --register,-r Register a new ticket. Use with -P, -v, -x,\n" |
| " -e, -a, -K, -H, -g.\n" |
| "Action parameters:\n" |
| " --brand-key,-b Set the brand code key. Use with -P and -B.\n" |
| " Value must be empty or KSBrandID.\n" |
| " --brand-path,-B Set the brand code path. Use with -P and -b.\n" |
| " --productid,-P id ProductID.\n" |
| " --system-store,-S Use the system-wide ticket store.\n" |
| " --tag,-g TAG Set the tag. Use with -P.\n" |
| " --tag-key,-K Set the tag path key. Use with -P and -H.\n" |
| " --tag-path,-H Set the tag path. Use with -P and -K.\n" |
| " --user-initiated,-F This operation is initiated by a user.\n" |
| " --user-store,-U Use a per-user ticket store.\n" |
| " --version,-v VERS Set the version. Use with -P.\n" |
| " --version-key,-e Set the version path key. Use with -P and -a.\n" |
| " --version-path,-a Set the version path. Use with -P and -e.\n" |
| " --xcpath,-x PATH Set a path to use as an existence checker.\n"; |
| printf("%s\n", usage_message.c_str()); |
| Shutdown(error_message.empty() ? 0 : 1); |
| } |
| |
| void KSAdminApp::Register() { |
| RegistrationRequest registration; |
| registration.app_id = SwitchValue(kCommandProductId); |
| registration.ap = SwitchValue(kCommandTag); |
| registration.brand_path = base::FilePath(SwitchValue(kCommandBrandPath)); |
| registration.version = base::Version(SwitchValue(kCommandVersion)); |
| registration.existence_checker_path = |
| base::FilePath(SwitchValue(kCommandXCPath)); |
| |
| const std::string brand_key = SwitchValue(kCommandBrandKey); |
| if (!brand_key.empty() && |
| brand_key != base::SysNSStringToUTF8(kCRUTicketBrandKey)) { |
| PrintUsage("Unsupported brand key."); |
| return; |
| } |
| |
| const std::string tag_key = SwitchValue(kCommandTagKey); |
| const std::string tag_path = SwitchValue(kCommandTagPath); |
| if (tag_key.empty() != tag_path.empty()) { |
| PrintUsage("--tag-key must be set if and only if --tag_path is set."); |
| return; |
| } |
| if (!tag_key.empty()) { |
| registration.ap = ReadPlist(tag_path, tag_key); |
| } |
| |
| const std::string version_key = SwitchValue(kCommandVersionKey); |
| const std::string version_path = SwitchValue(kCommandVersionPath); |
| if (version_key.empty() != version_path.empty()) { |
| PrintUsage( |
| "--version-key must be set if and only if --version_path is set."); |
| return; |
| } |
| if (!version_key.empty()) { |
| registration.version = base::Version(ReadPlist(version_path, version_key)); |
| } |
| |
| if (registration.app_id.empty()) { |
| PrintUsage("--register requires -P."); |
| return; |
| } |
| |
| UpdaterScope scope = |
| geteuid() == 0 ? UpdaterScope::kSystem : UpdaterScope::kUser; |
| MaybeInstallUpdater(scope); |
| ServiceProxy(scope)->RegisterApp( |
| registration, base::BindOnce( |
| [](base::OnceCallback<void(int)> cb, int result) { |
| if (result == kRegistrationSuccess) { |
| std::move(cb).Run(0); |
| } else { |
| LOG(ERROR) |
| << "Updater registration error: " << result; |
| std::move(cb).Run(1); |
| } |
| }, |
| base::BindOnce(&KSAdminApp::Shutdown, this))); |
| } |
| |
| void KSAdminApp::UpdateApp() { |
| ChooseService(base::BindOnce(&KSAdminApp::DoUpdateApp, this)); |
| } |
| |
| void KSAdminApp::DoUpdateApp(UpdaterScope scope) { |
| std::string app_id = SwitchValue(kCommandProductId); |
| if (app_id.empty()) { |
| PrintUsage("productid missing"); |
| return; |
| } |
| |
| ServiceProxy(scope)->Update( |
| app_id, GetInstallDataIndexFromAppArgs(app_id), |
| HasSwitch(kCommandUserInitiated) ? UpdateService::Priority::kForeground |
| : UpdateService::Priority::kBackground, |
| UpdateService::PolicySameVersionUpdate::kNotAllowed, |
| base::BindRepeating([](const UpdateService::UpdateState& update_state) { |
| if (update_state.state == UpdateService::UpdateState::State::kUpdated) { |
| printf("Finished updating (errors=%d reboot=%s)\n", 0, "YES"); |
| } |
| }), |
| base::BindOnce( |
| [](base::OnceCallback<void(int)> cb, UpdateService::Result result) { |
| if (result == UpdateService::Result::kSuccess) { |
| printf("Available updates: (\n)\n"); |
| std::move(cb).Run(0); |
| } else { |
| LOG(ERROR) << "Error code: " << result; |
| std::move(cb).Run(1); |
| } |
| }, |
| base::BindOnce(&KSAdminApp::Shutdown, this))); |
| } |
| |
| void KSAdminApp::ListAppUpdate() { |
| ChooseService(base::BindOnce(&KSAdminApp::DoListAppUpdate, this)); |
| } |
| |
| void KSAdminApp::DoListAppUpdate(UpdaterScope scope) { |
| std::string app_id = SwitchValue(kCommandProductId); |
| if (app_id.empty()) { |
| PrintUsage("productid missing"); |
| return; |
| } |
| |
| auto update_check_result = base::MakeRefCounted<UpdateCheckResult>(app_id); |
| ServiceProxy(scope)->CheckForUpdate( |
| app_id, |
| HasSwitch(kCommandUserInitiated) ? UpdateService::Priority::kForeground |
| : UpdateService::Priority::kBackground, |
| UpdateService::PolicySameVersionUpdate::kNotAllowed, |
| base::BindRepeating( |
| [](scoped_refptr<UpdateCheckResult> update_check_result, |
| const UpdateService::UpdateState& update_state) { |
| if (update_state.state == |
| UpdateService::UpdateState::State::kUpdateAvailable) { |
| update_check_result->set_next_version( |
| update_state.next_version.GetString()); |
| } |
| }, |
| update_check_result), |
| base::BindOnce( |
| [](scoped_refptr<UpdateCheckResult> update_check_result, |
| base::OnceCallback<void(int)> cb, UpdateService::Result result) { |
| if (result == UpdateService::Result::kSuccess) { |
| // This output format must not be changed, because the Keystone |
| // Registration Framework is expecting the exact format. |
| printf("Available updates: (\n"); |
| if (!update_check_result->next_version().empty()) { |
| printf("\t{\n" |
| "\tkServerProductID = \"%s\";\n" |
| "\tkServerDisplayVersion = \"%s\";\n" |
| "\tkServerVersion = \"%s\";\n" |
| "\t}\n", |
| update_check_result->app_id().c_str(), |
| update_check_result->next_version().c_str(), |
| update_check_result->next_version().c_str()); |
| } |
| printf(")\n"); |
| std::move(cb).Run(0); |
| } else { |
| LOG(ERROR) << "Error code: " << result; |
| std::move(cb).Run(1); |
| } |
| }, |
| update_check_result, base::BindOnce(&KSAdminApp::Shutdown, this))); |
| } |
| |
| bool KSAdminApp::HasSwitch(const std::string& arg) const { |
| return updater::HasSwitch(arg, switches_); |
| } |
| |
| std::string KSAdminApp::SwitchValue(const std::string& arg) const { |
| return updater::SwitchValue(arg, switches_); |
| } |
| |
| void KSAdminApp::Delete() { |
| // Existing updater clients may call `ksadmin --delete` to delete an app |
| // ticket in one of the following situations: |
| // 1) The app is uninstalled. In this case, the path existence checker should |
| // return false. That means the app will be un-registered by the periodic |
| // tasks at certain point. |
| // 2) The user updater figures that the app is managed by the system updater |
| // as well. A common scenario is that a second user installed the same app |
| // and then promoted it to a system app. In this case, we can ignore the |
| // deletion request and just rely on the system updater to run update. |
| // The downside is that sometimes the user updater gets the app update |
| // error. But this could an existing problem when two user updaters manage |
| // the same app together. |
| // So in summary, we can just omit the ticket deletion request here. |
| Shutdown(0); |
| } |
| |
| NSDictionary<NSString*, KSTicket*>* KSAdminApp::LoadTicketStore() const { |
| return [KSTicketStore |
| readStoreWithPath:base::SysUTF8ToNSString(KeystoneTicketStorePath( |
| updater::Scope(switches_)))]; |
| } |
| |
| int KSAdminApp::PrintKeystoneTag(const std::string& app_id) const { |
| @autoreleasepool { |
| NSDictionary<NSString*, KSTicket*>* store = LoadTicketStore(); |
| KSTicket* ticket = |
| [store objectForKey:[base::SysUTF8ToNSString(app_id) lowercaseString]]; |
| if (ticket) { |
| printf("%s\n", base::SysNSStringToUTF8([ticket determineTag]).c_str()); |
| } else { |
| printf("No ticket for %s\n", app_id.c_str()); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| void KSAdminApp::PrintTag() { |
| ChooseService(base::BindOnce(&KSAdminApp::DoPrintTag, this)); |
| } |
| |
| void KSAdminApp::DoPrintTag(UpdaterScope scope) { |
| const std::string app_id = SwitchValue(kCommandProductId); |
| if (app_id.empty()) { |
| PrintUsage("productid missing"); |
| return; |
| } |
| |
| ServiceProxy(scope)->GetAppStates(base::BindOnce( |
| [](const std::string& app_id, |
| base::OnceCallback<int(const std::string&)> fallback_cb, |
| base::OnceCallback<void(int)> done_cb, |
| const std::vector<updater::UpdateService::AppState>& states) { |
| int exit_code = 0; |
| |
| std::vector<updater::UpdateService::AppState>::const_iterator it = |
| base::ranges::find_if( |
| states, |
| [&app_id](const updater::UpdateService::AppState& state) { |
| return base::EqualsCaseInsensitiveASCII(state.app_id, app_id); |
| }); |
| if (it != std::end(states)) { |
| KSTicket* ticket = TicketFromAppState(*it); |
| printf("%s\n", |
| base::SysNSStringToUTF8([ticket determineTag]).c_str()); |
| |
| } else { |
| // Fallback to print tag from legacy Keystone tickets if there's no |
| // matching app registered with the Chromium updater. |
| exit_code = std::move(fallback_cb).Run(app_id); |
| } |
| |
| std::move(done_cb).Run(exit_code); |
| }, |
| app_id, base::BindOnce(&KSAdminApp::PrintKeystoneTag, this), |
| base::BindOnce(&KSAdminApp::Shutdown, this))); |
| } |
| |
| void KSAdminApp::PrintVersion() { |
| printf("%s\n", kUpdaterVersion); |
| Shutdown(0); |
| } |
| |
| bool KSAdminApp::PrintKeystoneTickets(const std::string& app_id) const { |
| // Print all tickets if `app_id` is empty. Otherwise only print ticket for |
| // the given app id. |
| @autoreleasepool { |
| NSDictionary<NSString*, KSTicket*>* store = LoadTicketStore(); |
| if (app_id.empty()) { |
| if (store.count > 0) { |
| for (NSString* key in store) { |
| printf("%s\n", |
| base::SysNSStringToUTF8([store[key] description]).c_str()); |
| } |
| return true; |
| } |
| } else { |
| KSTicket* ticket = [store |
| objectForKey:[base::SysUTF8ToNSString(app_id) lowercaseString]]; |
| if (ticket) { |
| printf("%s\n", base::SysNSStringToUTF8([ticket description]).c_str()); |
| return true; |
| } |
| } |
| |
| printf("No tickets.\n"); |
| return false; |
| } |
| } |
| |
| void KSAdminApp::PrintTickets() { |
| ChooseService(base::BindOnce(&KSAdminApp::DoPrintTickets, this)); |
| } |
| |
| void KSAdminApp::DoPrintTickets(UpdaterScope scope) { |
| const std::string app_id = SwitchValue(kCommandProductId); |
| ServiceProxy(scope)->GetAppStates(base::BindOnce( |
| [](const std::string& app_id, base::OnceCallback<bool()> fallback_cb, |
| base::OnceCallback<void(int)> done_cb, |
| const std::vector<updater::UpdateService::AppState>& states) { |
| bool ticket_printed = false; |
| for (const updater::UpdateService::AppState& state : states) { |
| if (!app_id.empty() && |
| !base::EqualsCaseInsensitiveASCII(app_id, state.app_id)) { |
| continue; |
| } |
| KSTicket* ticket = TicketFromAppState(state); |
| printf("%s\n", base::SysNSStringToUTF8([ticket description]).c_str()); |
| ticket_printed = true; |
| } |
| |
| // Fallback to print legacy Keystone tickets if there's no apps |
| // registered with the new updater. |
| if (states.empty()) { |
| ticket_printed = std::move(fallback_cb).Run(); |
| } |
| std::move(done_cb).Run(ticket_printed || app_id.empty() ? 0 : 1); |
| }, |
| app_id, base::BindOnce(&KSAdminApp::PrintKeystoneTickets, this, app_id), |
| base::BindOnce(&KSAdminApp::Shutdown, this))); |
| } |
| |
| void KSAdminApp::FirstTaskRun() { |
| if ((geteuid() == 0) && (getuid() != 0)) { |
| if (setuid(0) || setgid(0)) { |
| LOG(ERROR) << "Can't setuid()/setgid() appropriately."; |
| Shutdown(1); |
| } |
| } |
| const std::map<std::string, void (KSAdminApp::*)()> commands = { |
| {kCommandDelete, &KSAdminApp::Delete}, |
| {kCommandInstall, &KSAdminApp::UpdateApp}, |
| {kCommandList, &KSAdminApp::ListAppUpdate}, |
| {kCommandKsadminVersion, &KSAdminApp::PrintVersion}, |
| {kCommandPrintTag, &KSAdminApp::PrintTag}, |
| {kCommandPrintTickets, &KSAdminApp::PrintTickets}, |
| {kCommandRegister, &KSAdminApp::Register}, |
| }; |
| for (const auto& entry : commands) { |
| if (HasSwitch(entry.first)) { |
| (this->*entry.second)(); |
| return; |
| } |
| } |
| |
| // Fallbacks to Register: if none of the above exist, these switches signal |
| // an intent to register. |
| for (const std::string& command : |
| {kCommandTag, kCommandTagKey, kCommandTagPath, kCommandBrandKey, |
| kCommandBrandPath, kCommandVersion, kCommandVersionKey, |
| kCommandVersionPath, kCommandXCPath}) { |
| if (HasSwitch(command)) { |
| Register(); |
| return; |
| } |
| } |
| PrintUsage(""); |
| } |
| |
| } // namespace |
| |
| int KSAdminAppMain(int argc, const char* argv[]) { |
| base::AtExitManager exit_manager; |
| base::CommandLine::Init(argc, argv); |
| std::map<std::string, std::string> command_line = |
| ParseCommandLine(argc, argv); |
| updater::InitLogging(Scope(command_line)); |
| InitializeThreadPool("keystone"); |
| const base::ScopedClosureRunner shutdown_thread_pool( |
| base::BindOnce([] { base::ThreadPoolInstance::Get()->Shutdown(); })); |
| base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI); |
| |
| // base::CommandLine may reorder arguments and switches, this is not the exact |
| // command line. |
| VLOG(0) << base::CommandLine::ForCurrentProcess()->GetCommandLineString(); |
| |
| return base::MakeRefCounted<KSAdminApp>(command_line)->Run(); |
| } |
| |
| } // namespace updater |