| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "login_manager/session_manager_impl.h" |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <sys/socket.h> |
| |
| #include <algorithm> |
| #include <locale> |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/memory/ref_counted.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_tokenizer.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/cryptohome.h> |
| #include <crypto/scoped_nss_types.h> |
| #include <dbus/message.h> |
| |
| #include "bindings/chrome_device_policy.pb.h" |
| #include "bindings/device_management_backend.pb.h" |
| #include "login_manager/crossystem.h" |
| #include "login_manager/dbus_error_types.h" |
| #include "login_manager/dbus_signal_emitter.h" |
| #include "login_manager/device_local_account_policy_service.h" |
| #include "login_manager/device_policy_service.h" |
| #include "login_manager/key_generator.h" |
| #include "login_manager/login_metrics.h" |
| #include "login_manager/nss_util.h" |
| #include "login_manager/policy_key.h" |
| #include "login_manager/policy_service.h" |
| #include "login_manager/process_manager_service_interface.h" |
| #include "login_manager/regen_mitigator.h" |
| #include "login_manager/system_utils.h" |
| #include "login_manager/upstart_signal_emitter.h" |
| #include "login_manager/user_policy_service_factory.h" |
| #include "login_manager/vpd_process.h" |
| |
| using base::FilePath; |
| using brillo::cryptohome::home::GetRootPath; |
| using brillo::cryptohome::home::GetUserPath; |
| using brillo::cryptohome::home::SanitizeUserName; |
| using brillo::cryptohome::home::kGuestUserName; |
| |
| namespace login_manager { // NOLINT |
| |
| const char SessionManagerImpl::kDemoUser[] = "demouser@"; |
| |
| const char SessionManagerImpl::kStarted[] = "started"; |
| const char SessionManagerImpl::kStopping[] = "stopping"; |
| const char SessionManagerImpl::kStopped[] = "stopped"; |
| |
| const char SessionManagerImpl::kLoggedInFlag[] = |
| "/var/run/session_manager/logged_in"; |
| const char SessionManagerImpl::kResetFile[] = |
| "/mnt/stateful_partition/factory_install_reset"; |
| |
| const char SessionManagerImpl::kArcContainerName[] = "android"; |
| const char SessionManagerImpl::kArcStartSignal[] = "start-arc-instance"; |
| const char SessionManagerImpl::kArcStopSignal[] = "stop-arc-instance"; |
| const char SessionManagerImpl::kArcNetworkStartSignal[] = "start-arc-network"; |
| const char SessionManagerImpl::kArcNetworkStopSignal[] = "stop-arc-network"; |
| |
| const base::FilePath::CharType SessionManagerImpl::kAndroidDataDirName[] = |
| FILE_PATH_LITERAL("android-data"); |
| |
| namespace { |
| |
| // Constants used in email validation. |
| const char kEmailSeparator = '@'; |
| const char kEmailLegalCharacters[] = |
| "abcdefghijklmnopqrstuvwxyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| ".@1234567890!#$%&'*+-/=?^_`{|}~"; |
| |
| // Should match chromium AccountId::kKeyGaiaIdPrefix . |
| const char kGaiaIdKeyPrefix[] = "g-"; |
| const char kGaiaIdKeyLegalCharacters[] = |
| "-0123456789" |
| "abcdefghijklmnopqrstuvwxyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| |
| // The flag to pass to chrome to open a named socket for testing. |
| const char kTestingChannelFlag[] = "--testing-channel=NamedTestingInterface:"; |
| |
| // Device-local account state directory. |
| const base::FilePath::CharType kDeviceLocalAccountStateDir[] = |
| FILE_PATH_LITERAL("/var/lib/device_local_accounts"); |
| |
| // SystemUtils::EnsureJobExit() DCHECKs if the timeout is zero, so this is the |
| // minimum amount of time we must wait before killing the containers. |
| constexpr int kContainerTimeoutSec = 1; |
| |
| bool IsIncognitoAccountId(const std::string& account_id) { |
| const std::string lower_case_id(base::ToLowerASCII(account_id)); |
| return (lower_case_id == kGuestUserName) || |
| (lower_case_id == SessionManagerImpl::kDemoUser); |
| } |
| |
| #if USE_CHEETS |
| base::FilePath GetAndroidDataDirForUser( |
| const std::string& normalized_account_id) { |
| return GetRootPath(normalized_account_id).Append( |
| SessionManagerImpl::kAndroidDataDirName); |
| } |
| #endif // USE_CHEETS |
| |
| } // namespace |
| |
| SessionManagerImpl::Error::Error() : set_(false) {} |
| |
| SessionManagerImpl::Error::Error(const std::string& name, |
| const std::string& message) |
| : name_(name), message_(message), set_(true) {} |
| |
| SessionManagerImpl::Error::~Error() {} |
| |
| void SessionManagerImpl::Error::Set(const std::string& name, |
| const std::string& message) { |
| name_ = name; |
| message_ = message; |
| set_ = true; |
| } |
| |
| struct SessionManagerImpl::UserSession { |
| public: |
| UserSession(const std::string& username, |
| const std::string& userhash, |
| bool is_incognito, |
| crypto::ScopedPK11Slot slot, |
| std::unique_ptr<PolicyService> policy_service) |
| : username(username), |
| userhash(userhash), |
| is_incognito(is_incognito), |
| slot(std::move(slot)), |
| policy_service(std::move(policy_service)) {} |
| ~UserSession() {} |
| |
| const std::string username; |
| const std::string userhash; |
| const bool is_incognito; |
| crypto::ScopedPK11Slot slot; |
| std::unique_ptr<PolicyService> policy_service; |
| }; |
| |
| SessionManagerImpl::SessionManagerImpl( |
| scoped_ptr<InitDaemonController> init_controller, |
| DBusSignalEmitterInterface* dbus_emitter, |
| base::Closure lock_screen_closure, |
| base::Closure restart_device_closure, |
| base::Closure start_arc_instance_closure, |
| KeyGenerator* key_gen, |
| ServerBackedStateKeyGenerator* state_key_generator, |
| ProcessManagerServiceInterface* manager, |
| LoginMetrics* metrics, |
| NssUtil* nss, |
| SystemUtils* utils, |
| Crossystem* crossystem, |
| VpdProcess* vpd_process, |
| PolicyKey* owner_key, |
| ContainerManagerInterface* android_container) |
| : session_started_(false), |
| session_stopping_(false), |
| screen_locked_(false), |
| supervised_user_creation_ongoing_(false), |
| init_controller_(std::move(init_controller)), |
| lock_screen_closure_(lock_screen_closure), |
| restart_device_closure_(restart_device_closure), |
| start_arc_instance_closure_(start_arc_instance_closure), |
| dbus_emitter_(dbus_emitter), |
| key_gen_(key_gen), |
| state_key_generator_(state_key_generator), |
| manager_(manager), |
| login_metrics_(metrics), |
| nss_(nss), |
| system_(utils), |
| crossystem_(crossystem), |
| vpd_process_(vpd_process), |
| owner_key_(owner_key), |
| android_container_(android_container), |
| mitigator_(key_gen), |
| weak_ptr_factory_(this) {} |
| |
| SessionManagerImpl::~SessionManagerImpl() { |
| STLDeleteValues(&user_sessions_); |
| device_policy_->set_delegate(NULL); // Could use WeakPtr instead? |
| } |
| |
| void SessionManagerImpl::InjectPolicyServices( |
| std::unique_ptr<DevicePolicyService> device_policy, |
| std::unique_ptr<UserPolicyServiceFactory> user_policy_factory, |
| std::unique_ptr<DeviceLocalAccountPolicyService> |
| device_local_account_policy) { |
| device_policy_ = std::move(device_policy); |
| user_policy_factory_ = std::move(user_policy_factory); |
| device_local_account_policy_ = std::move(device_local_account_policy); |
| } |
| |
| void SessionManagerImpl::AnnounceSessionStoppingIfNeeded() { |
| if (session_started_) { |
| session_stopping_ = true; |
| DLOG(INFO) << "emitting D-Bus signal SessionStateChanged:" << kStopping; |
| dbus_emitter_->EmitSignalWithString(kSessionStateChangedSignal, kStopping); |
| } |
| } |
| |
| void SessionManagerImpl::AnnounceSessionStopped() { |
| session_stopping_ = session_started_ = false; |
| DLOG(INFO) << "emitting D-Bus signal SessionStateChanged:" << kStopped; |
| dbus_emitter_->EmitSignalWithString(kSessionStateChangedSignal, kStopped); |
| } |
| |
| bool SessionManagerImpl::ShouldEndSession() { |
| return screen_locked_ || supervised_user_creation_ongoing_; |
| } |
| |
| bool SessionManagerImpl::Initialize() { |
| key_gen_->set_delegate(this); |
| |
| device_policy_.reset(DevicePolicyService::Create(login_metrics_, owner_key_, |
| &mitigator_, nss_, |
| crossystem_, vpd_process_)); |
| device_policy_->set_delegate(this); |
| |
| user_policy_factory_.reset( |
| new UserPolicyServiceFactory(getuid(), nss_, system_)); |
| device_local_account_policy_.reset(new DeviceLocalAccountPolicyService( |
| base::FilePath(kDeviceLocalAccountStateDir), owner_key_)); |
| |
| if (device_policy_->Initialize()) { |
| device_local_account_policy_->UpdateDeviceSettings( |
| device_policy_->GetSettings()); |
| if (device_policy_->MayUpdateSystemSettings()) { |
| device_policy_->UpdateSystemSettings(PolicyService::Completion()); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| void SessionManagerImpl::Finalize() { |
| device_policy_->PersistPolicySync(); |
| for (UserSessionMap::const_iterator it = user_sessions_.begin(); |
| it != user_sessions_.end(); ++it) { |
| if (it->second) |
| it->second->policy_service->PersistPolicySync(); |
| } |
| // We want to stop any running containers. Containers are per-session and |
| // cannot persist across sessions. |
| android_container_->RequestJobExit(); |
| android_container_->EnsureJobExit( |
| base::TimeDelta::FromSeconds(kContainerTimeoutSec)); |
| } |
| |
| void SessionManagerImpl::EmitLoginPromptVisible(Error* error) { |
| login_metrics_->RecordStats("login-prompt-visible"); |
| dbus_emitter_->EmitSignal(kLoginPromptVisibleSignal); |
| scoped_ptr<dbus::Response> response = init_controller_->TriggerImpulse( |
| "login-prompt-visible", std::vector<std::string>()); |
| if (!response) { |
| static const char msg[] = |
| "Emitting login-prompt-visible upstart signal failed."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kEmitFailed, msg); |
| } |
| } |
| |
| std::string SessionManagerImpl::EnableChromeTesting( |
| bool relaunch, |
| std::vector<std::string> extra_args, |
| Error* error) { |
| // Check to see if we already have Chrome testing enabled. |
| bool already_enabled = !chrome_testing_path_.empty(); |
| |
| if (!already_enabled) { |
| base::FilePath temp_file_path; // So we don't clobber chrome_testing_path_; |
| if (!system_->GetUniqueFilenameInWriteOnlyTempDir(&temp_file_path)) { |
| error->Set(dbus_error::kTestingChannelError, |
| "Could not create testing channel filename."); |
| return std::string(); |
| } |
| chrome_testing_path_ = temp_file_path; |
| } |
| |
| if (!already_enabled || relaunch) { |
| // Delete testing channel file if it already exists. |
| system_->RemoveFile(chrome_testing_path_); |
| |
| // Add testing channel argument to extra arguments. |
| std::string testing_argument = kTestingChannelFlag; |
| testing_argument.append(chrome_testing_path_.value()); |
| extra_args.push_back(testing_argument); |
| |
| manager_->RestartBrowserWithArgs(extra_args, true); |
| } |
| return chrome_testing_path_.value(); |
| } |
| |
| bool SessionManagerImpl::StartSession(const std::string& account_id, |
| const std::string& unique_id, |
| Error* error) { |
| std::string actual_account_id; |
| if (!NormalizeAccountId(account_id, &actual_account_id, error)) { |
| return false; |
| } |
| |
| // Check if this user already started a session. |
| if (user_sessions_.count(actual_account_id) > 0) { |
| static const char msg[] = "Provided user id already started a session."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSessionExists, msg); |
| return false; |
| } |
| |
| // Create a UserSession object for this user. |
| const bool is_incognito = IsIncognitoAccountId(actual_account_id); |
| std::string error_name; |
| std::unique_ptr<UserSession> user_session( |
| CreateUserSession(actual_account_id, is_incognito, &error_name)); |
| if (!user_session.get()) { |
| error->Set(error_name, "Can't create session."); |
| return false; |
| } |
| // Check whether the current user is the owner, and if so make sure she is |
| // whitelisted and has an owner key. |
| bool user_is_owner = false; |
| PolicyService::Error policy_error; |
| if (!device_policy_->CheckAndHandleOwnerLogin( |
| user_session->username, user_session->slot.get(), &user_is_owner, |
| &policy_error)) { |
| error->Set(policy_error.code(), policy_error.message()); |
| return false; |
| } |
| |
| // If all previous sessions were incognito (or no previous sessions exist). |
| bool is_first_real_user = AllSessionsAreIncognito() && !is_incognito; |
| |
| // Send each user login event to UMA (right before we start session |
| // since the metrics library does not log events in guest mode). |
| int dev_mode = system_->IsDevMode(); |
| if (dev_mode > -1) |
| login_metrics_->SendLoginUserType(dev_mode, is_incognito, user_is_owner); |
| |
| scoped_ptr<dbus::Response> response = init_controller_->TriggerImpulse( |
| "start-user-session", |
| std::vector<std::string>(1, "CHROMEOS_USER=" + actual_account_id)); |
| |
| if (!response) { |
| static const char msg[] = |
| "Emitting start-user-session upstart signal failed."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kEmitFailed, msg); |
| return false; |
| } |
| LOG(INFO) << "Starting user session"; |
| manager_->SetBrowserSessionForUser(actual_account_id, user_session->userhash); |
| session_started_ = true; |
| user_sessions_[actual_account_id] = user_session.release(); |
| DLOG(INFO) << "emitting D-Bus signal SessionStateChanged:" << kStarted; |
| dbus_emitter_->EmitSignalWithString(kSessionStateChangedSignal, kStarted); |
| |
| if (device_policy_->KeyMissing() && !device_policy_->Mitigating() && |
| is_first_real_user) { |
| // This is the first sign-in on this unmanaged device. Take ownership. |
| key_gen_->Start(actual_account_id); |
| } |
| |
| // Record that a login has successfully completed on this boot. |
| system_->AtomicFileWrite(base::FilePath(kLoggedInFlag), "1"); |
| return true; |
| } |
| |
| bool SessionManagerImpl::StopSession() { |
| LOG(INFO) << "Stopping all sessions"; |
| // Most calls to StopSession() will log the reason for the call. |
| // If you don't see a log message saying the reason for the call, it is |
| // likely a DBUS message. See session_manager_dbus_adaptor.cc for that call. |
| manager_->ScheduleShutdown(); |
| // TODO(cmasone): re-enable these when we try to enable logout without exiting |
| // the session manager |
| // browser_.job->StopSession(); |
| // user_policy_.reset(); |
| // session_started_ = false; |
| return true; |
| } |
| |
| void SessionManagerImpl::StorePolicy( |
| const uint8_t* policy_blob, |
| size_t policy_blob_len, |
| const PolicyService::Completion& completion) { |
| int flags = PolicyService::KEY_ROTATE; |
| if (!session_started_) |
| flags |= PolicyService::KEY_INSTALL_NEW | PolicyService::KEY_CLOBBER; |
| device_policy_->Store(policy_blob, policy_blob_len, completion, flags); |
| } |
| |
| void SessionManagerImpl::RetrievePolicy(std::vector<uint8_t>* policy_data, |
| Error* error) { |
| if (!device_policy_->Retrieve(policy_data)) { |
| static const char msg[] = "Failed to retrieve policy data."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSigEncodeFail, msg); |
| return; |
| } |
| } |
| |
| void SessionManagerImpl::StorePolicyForUser( |
| const std::string& account_id, |
| const uint8_t* policy_blob, |
| size_t policy_blob_len, |
| const PolicyService::Completion& completion) { |
| PolicyService* policy_service = GetPolicyService(account_id); |
| if (!policy_service) { |
| PolicyService::Error error( |
| dbus_error::kSessionDoesNotExist, |
| "Cannot store user policy before session is started."); |
| LOG(ERROR) << error.message(); |
| completion.Run(error); |
| return; |
| } |
| |
| policy_service->Store( |
| policy_blob, policy_blob_len, completion, |
| PolicyService::KEY_INSTALL_NEW | PolicyService::KEY_ROTATE); |
| } |
| |
| void SessionManagerImpl::RetrievePolicyForUser( |
| const std::string& account_id, |
| std::vector<uint8_t>* policy_data, |
| Error* error) { |
| PolicyService* policy_service = GetPolicyService(account_id); |
| if (!policy_service) { |
| static const char msg[] = |
| "Cannot retrieve user policy before session is started."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSessionDoesNotExist, msg); |
| return; |
| } |
| if (!policy_service->Retrieve(policy_data)) { |
| static const char msg[] = "Failed to retrieve policy data."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSigEncodeFail, msg); |
| } |
| } |
| |
| void SessionManagerImpl::StoreDeviceLocalAccountPolicy( |
| const std::string& account_id, |
| const uint8_t* policy_blob, |
| size_t policy_blob_len, |
| const PolicyService::Completion& completion) { |
| device_local_account_policy_->Store(account_id, policy_blob, policy_blob_len, |
| completion); |
| } |
| |
| void SessionManagerImpl::RetrieveDeviceLocalAccountPolicy( |
| const std::string& account_id, |
| std::vector<uint8_t>* policy_data, |
| Error* error) { |
| if (!device_local_account_policy_->Retrieve(account_id, policy_data)) { |
| static const char msg[] = "Failed to retrieve policy data."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSigEncodeFail, msg); |
| return; |
| } |
| } |
| |
| const char* SessionManagerImpl::RetrieveSessionState() { |
| if (!session_started_) |
| return kStopped; |
| else |
| return (session_stopping_ ? kStopping : kStarted); |
| } |
| |
| void SessionManagerImpl::RetrieveActiveSessions( |
| std::map<std::string, std::string>* active_sessions) { |
| for (UserSessionMap::const_iterator it = user_sessions_.begin(); |
| it != user_sessions_.end(); ++it) { |
| if (it->second) { |
| (*active_sessions)[it->second->username] = it->second->userhash; |
| } |
| } |
| } |
| |
| void SessionManagerImpl::HandleSupervisedUserCreationStarting() { |
| supervised_user_creation_ongoing_ = true; |
| } |
| |
| void SessionManagerImpl::HandleSupervisedUserCreationFinished() { |
| supervised_user_creation_ongoing_ = false; |
| } |
| |
| void SessionManagerImpl::LockScreen(Error* error) { |
| if (!session_started_) { |
| static const char msg[] = "Attempt to lock screen outside of user session."; |
| LOG(WARNING) << msg; |
| error->Set(dbus_error::kSessionDoesNotExist, msg); |
| return; |
| } |
| // If all sessions are incognito, then locking is not allowed. |
| if (AllSessionsAreIncognito()) { |
| static const char msg[] = "Attempt to lock screen during Guest session."; |
| LOG(WARNING) << msg; |
| error->Set(dbus_error::kSessionExists, msg); |
| return; |
| } |
| if (!screen_locked_) { |
| screen_locked_ = true; |
| lock_screen_closure_.Run(); |
| } |
| LOG(INFO) << "LockScreen() method called."; |
| } |
| |
| void SessionManagerImpl::HandleLockScreenShown() { |
| LOG(INFO) << "HandleLockScreenShown() method called."; |
| dbus_emitter_->EmitSignal(kScreenIsLockedSignal); |
| } |
| |
| void SessionManagerImpl::HandleLockScreenDismissed() { |
| screen_locked_ = false; |
| LOG(INFO) << "HandleLockScreenDismissed() method called."; |
| dbus_emitter_->EmitSignal(kScreenIsUnlockedSignal); |
| } |
| |
| bool SessionManagerImpl::RestartJob(int fd, |
| const std::vector<std::string>& argv, |
| Error* error) { |
| struct ucred ucred = {0}; |
| socklen_t len = sizeof(struct ucred); |
| if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) { |
| PLOG(ERROR) << "Can't get peer creds"; |
| error->Set("GetPeerCredsFailed", strerror(errno)); |
| return false; |
| } |
| |
| if (!manager_->IsBrowser(ucred.pid)) { |
| static const char msg[] = "Provided pid is unknown."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kUnknownPid, msg); |
| return false; |
| } |
| |
| // To set "logged-in" state for BWSI mode. |
| if (!StartSession(kGuestUserName, "", error)) |
| return false; |
| manager_->RestartBrowserWithArgs(argv, false); |
| return true; |
| } |
| |
| void SessionManagerImpl::StartDeviceWipe(const std::string& reason, |
| Error* error) { |
| const base::FilePath session_path(kLoggedInFlag); |
| if (system_->Exists(session_path)) { |
| static const char msg[] = "A user has already logged in this boot."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSessionExists, msg); |
| return; |
| } |
| InitiateDeviceWipe(reason); |
| } |
| |
| void SessionManagerImpl::SetFlagsForUser( |
| const std::string& account_id, |
| const std::vector<std::string>& session_user_flags) { |
| manager_->SetFlagsForUser(account_id, session_user_flags); |
| } |
| |
| void SessionManagerImpl::RequestServerBackedStateKeys( |
| const ServerBackedStateKeyGenerator::StateKeyCallback& callback) { |
| state_key_generator_->RequestStateKeys(callback); |
| } |
| |
| void SessionManagerImpl::InitMachineInfo(const std::string& data, |
| Error* error) { |
| std::map<std::string, std::string> params; |
| if (!ServerBackedStateKeyGenerator::ParseMachineInfo(data, ¶ms)) |
| error->Set(dbus_error::kInitMachineInfoFail, "Parse failure."); |
| |
| if (!state_key_generator_->InitMachineInfo(params)) |
| error->Set(dbus_error::kInitMachineInfoFail, "Missing parameters."); |
| } |
| |
| void SessionManagerImpl::StartArcInstance(const std::string& account_id, |
| Error* error) { |
| #if USE_CHEETS |
| arc_start_time_ = base::TimeTicks::Now(); |
| |
| std::string actual_account_id; |
| if (!NormalizeAccountId(account_id, &actual_account_id, error)) { |
| return; |
| } |
| if (user_sessions_.count(actual_account_id) == 0) { |
| static const char msg[] = "Provided user ID does not have a session."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kSessionDoesNotExist, msg); |
| return; |
| } |
| |
| const base::FilePath android_data_dir = |
| GetAndroidDataDirForUser(actual_account_id); |
| const std::vector<std::string>& keyvals = { |
| base::StringPrintf("ANDROID_DATA_DIR=%s", |
| android_data_dir.value().c_str()), |
| base::StringPrintf("CHROMEOS_USER=%s", actual_account_id.c_str()), |
| }; |
| |
| if (!init_controller_->TriggerImpulse(kArcStartSignal, keyvals)) { |
| static const char msg[] = "Emitting start-arc-instance signal failed."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kEmitFailed, msg); |
| return; |
| } |
| |
| bool started_container = false; |
| const char* dbus_error = nullptr; |
| std::string error_message; |
| if (!StartArcInstanceInternal(&started_container, &dbus_error, |
| &error_message)) { |
| LOG(ERROR) << error_message; |
| error->Set(dbus_error, error_message); |
| init_controller_->TriggerImpulse(kArcStopSignal, |
| std::vector<std::string>()); |
| if (started_container) { |
| android_container_->RequestJobExit(); |
| android_container_->EnsureJobExit( |
| base::TimeDelta::FromSeconds(kContainerTimeoutSec)); |
| } |
| return; |
| } |
| start_arc_instance_closure_.Run(); |
| #else |
| error->Set(dbus_error::kNotAvailable, "ARC not supported."); |
| #endif // !USE_CHEETS |
| } |
| |
| void SessionManagerImpl::StopArcInstance(Error* error) { |
| #if USE_CHEETS |
| pid_t pid; |
| if (!android_container_->GetContainerPID(&pid)) { |
| static const char msg[] = "Error getting Android container pid."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kContainerShutdownFail, msg); |
| return; |
| } |
| |
| android_container_->RequestJobExit(); |
| android_container_->EnsureJobExit( |
| base::TimeDelta::FromSeconds(kContainerTimeoutSec)); |
| #else |
| error->Set(dbus_error::kNotAvailable, "ARC not supported."); |
| #endif // USE_CHEETS |
| } |
| |
| base::TimeTicks SessionManagerImpl::GetArcStartTime(Error* error) { |
| #if USE_CHEETS |
| if (arc_start_time_.is_null()) |
| error->Set(dbus_error::kNotStarted, "ARC is not started yet."); |
| #else |
| error->Set(dbus_error::kNotAvailable, "ARC not supported."); |
| #endif // !USE_CHEETS |
| return arc_start_time_; |
| } |
| |
| void SessionManagerImpl::StartContainer(const std::string& name, Error* error) { |
| // TODO(dgreid): Add support for other containers. |
| const char msg[] = "Container not found."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kContainerStartupFail, msg); |
| } |
| |
| void SessionManagerImpl::StopContainer(const std::string& name, Error* error) { |
| // TODO(dgreid): Add support for other containers. |
| const char msg[] = "Container not found."; |
| LOG(ERROR) << msg; |
| error->Set(dbus_error::kContainerShutdownFail, msg); |
| } |
| |
| void SessionManagerImpl::RemoveArcData(const std::string& account_id, |
| Error* error) { |
| #if USE_CHEETS |
| pid_t pid = 0; |
| if (android_container_->GetContainerPID(&pid)) { |
| error->Set(dbus_error::kArcInstanceRunning, "ARC is currently running."); |
| return; |
| } |
| |
| std::string actual_account_id; |
| if (!NormalizeAccountId(account_id, &actual_account_id, error)) { |
| return; |
| } |
| const base::FilePath android_data_dir = |
| GetAndroidDataDirForUser(actual_account_id); |
| system_->RemoveDirTree(android_data_dir); |
| #else |
| error->Set(dbus_error::kNotAvailable, "ARC not supported."); |
| #endif // USE_CHEETS |
| } |
| |
| void SessionManagerImpl::OnPolicyPersisted(bool success) { |
| device_local_account_policy_->UpdateDeviceSettings( |
| device_policy_->GetSettings()); |
| dbus_emitter_->EmitSignalWithSuccessFailure(kPropertyChangeCompleteSignal, |
| success); |
| } |
| |
| void SessionManagerImpl::OnKeyPersisted(bool success) { |
| dbus_emitter_->EmitSignalWithSuccessFailure(kOwnerKeySetSignal, success); |
| } |
| |
| void SessionManagerImpl::OnKeyGenerated(const std::string& username, |
| const base::FilePath& temp_key_file) { |
| ImportValidateAndStoreGeneratedKey(username, temp_key_file); |
| } |
| |
| void SessionManagerImpl::ImportValidateAndStoreGeneratedKey( |
| const std::string& username, |
| const base::FilePath& temp_key_file) { |
| DLOG(INFO) << "Processing generated key at " << temp_key_file.value(); |
| std::string key; |
| base::ReadFileToString(temp_key_file, &key); |
| PLOG_IF(WARNING, !base::DeleteFile(temp_key_file, false)) |
| << "Can't delete " << temp_key_file.value(); |
| device_policy_->ValidateAndStoreOwnerKey( |
| username, key, user_sessions_[username]->slot.get()); |
| } |
| |
| void SessionManagerImpl::InitiateDeviceWipe(const std::string& reason) { |
| // The log string must not be confused with other clobbers-state parameters. |
| // Sanitize by replacing all non-alphanumeric characters with underscores and |
| // clamping size to 50 characters. |
| std::string sanitized_reason(reason.substr(0, 50)); |
| std::locale locale("C"); |
| std::replace_if(sanitized_reason.begin(), sanitized_reason.end(), |
| [&locale](const std::string::value_type character) { |
| return !std::isalnum(character, locale); |
| }, |
| '_'); |
| const base::FilePath reset_path(kResetFile); |
| system_->AtomicFileWrite(reset_path, |
| "fast safe keepimg reason=" + sanitized_reason); |
| restart_device_closure_.Run(); |
| } |
| |
| // static |
| bool SessionManagerImpl::NormalizeAccountId(const std::string& account_id, |
| std::string* actual_account_id_out, |
| Error* error_out) { |
| // Validate the |account_id|. |
| if (IsIncognitoAccountId(account_id) || ValidateGaiaIdKey(account_id)) { |
| *actual_account_id_out = account_id; |
| return true; |
| } |
| |
| // Support legacy email addresses. |
| // TODO(alemate): remove this after ChromeOS will stop using email as |
| // cryptohome identifier. |
| const std::string& lower_email = base::ToLowerASCII(account_id); |
| if (!ValidateEmail(lower_email)) { |
| static const char msg[] = |
| "Provided email address is not valid. ASCII only."; |
| LOG(ERROR) << msg; |
| error_out->Set(dbus_error::kInvalidAccount, msg); |
| return false; |
| } |
| *actual_account_id_out = lower_email; |
| return true; |
| } |
| |
| // static |
| bool SessionManagerImpl::ValidateGaiaIdKey(const std::string& account_id) { |
| if (account_id.find_first_not_of(kGaiaIdKeyLegalCharacters) |
| != std::string::npos) |
| return false; |
| |
| return base::StartsWith( |
| account_id, kGaiaIdKeyPrefix, base::CompareCase::SENSITIVE); |
| } |
| |
| // static |
| bool SessionManagerImpl::ValidateEmail(const std::string& email_address) { |
| if (email_address.find_first_not_of(kEmailLegalCharacters) != |
| std::string::npos) { |
| return false; |
| } |
| |
| size_t at = email_address.find(kEmailSeparator); |
| // it has NO @. |
| if (at == std::string::npos) |
| return false; |
| |
| // it has more than one @. |
| if (email_address.find(kEmailSeparator, at + 1) != std::string::npos) |
| return false; |
| |
| return true; |
| } |
| |
| bool SessionManagerImpl::AllSessionsAreIncognito() { |
| size_t incognito_count = 0; |
| for (UserSessionMap::const_iterator it = user_sessions_.begin(); |
| it != user_sessions_.end(); ++it) { |
| if (it->second) |
| incognito_count += it->second->is_incognito; |
| } |
| return incognito_count == user_sessions_.size(); |
| } |
| |
| SessionManagerImpl::UserSession* SessionManagerImpl::CreateUserSession( |
| const std::string& username, |
| bool is_incognito, |
| std::string* error_message) { |
| std::unique_ptr<PolicyService> user_policy( |
| user_policy_factory_->Create(username)); |
| if (!user_policy) { |
| LOG(ERROR) << "User policy failed to initialize."; |
| if (error_message) |
| *error_message = dbus_error::kPolicyInitFail; |
| return NULL; |
| } |
| crypto::ScopedPK11Slot slot(nss_->OpenUserDB(GetUserPath(username))); |
| if (!slot) { |
| LOG(ERROR) << "Could not open the current user's NSS database."; |
| if (error_message) |
| *error_message = dbus_error::kNoUserNssDb; |
| return NULL; |
| } |
| return new SessionManagerImpl::UserSession( |
| username, SanitizeUserName(username), is_incognito, std::move(slot), |
| std::move(user_policy)); |
| } |
| |
| PolicyService* SessionManagerImpl::GetPolicyService(const std::string& user) { |
| UserSessionMap::const_iterator it = user_sessions_.find(user); |
| return it == user_sessions_.end() ? NULL : it->second->policy_service.get(); |
| } |
| |
| bool SessionManagerImpl::StartArcInstanceInternal( |
| bool* started_container_out, |
| const char** dbus_error_out, |
| std::string* error_message_out) { |
| *started_container_out = false; |
| *dbus_error_out = nullptr; |
| error_message_out->clear(); |
| if (!android_container_->StartContainer( |
| base::Bind(&SessionManagerImpl::OnAndroidContainerStopped, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| *dbus_error_out = dbus_error::kContainerStartupFail; |
| *error_message_out = "Starting Android container failed."; |
| return false; |
| } |
| *started_container_out = true; |
| |
| login_metrics_->StartTrackingArcUseTime(); |
| |
| base::FilePath root_path; |
| pid_t pid = 0; |
| if (!android_container_->GetRootFsPath(&root_path) || |
| !android_container_->GetContainerPID(&pid)) { |
| *dbus_error_out = dbus_error::kContainerStartupFail; |
| *error_message_out = "Getting Android container info failed."; |
| return false; |
| } |
| |
| // Also tell init to configure the network. |
| std::vector<std::string> env; |
| env.emplace_back("CONTAINER_NAME=" + std::string(kArcContainerName)); |
| env.emplace_back("CONTAINER_PATH=" + root_path.value()); |
| env.emplace_back("CONTAINER_PID=" + std::to_string(pid)); |
| if (!init_controller_->TriggerImpulse(kArcNetworkStartSignal, env)) { |
| *dbus_error_out = dbus_error::kEmitFailed; |
| *error_message_out = "Emitting start-arc-network signal failed."; |
| return false; |
| } |
| LOG(INFO) << "Started Android container pid " << pid; |
| |
| return true; |
| } |
| |
| void SessionManagerImpl::OnAndroidContainerStopped(pid_t pid, bool clean) { |
| if (clean) { |
| LOG(INFO) << "Android Container with pid " << pid << " stopped"; |
| } else { |
| LOG(ERROR) << "Android Container with pid " << pid << " crashed"; |
| } |
| |
| login_metrics_->StopTrackingArcUseTime(); |
| |
| std::vector<std::string> env; |
| env.emplace_back("ANDROID_PID=" + std::to_string(pid)); |
| if (!init_controller_->TriggerImpulse(kArcStopSignal, env)) { |
| static const char msg[] = "Emitting stop-arc-instance init signal failed."; |
| LOG(ERROR) << msg; |
| } |
| |
| if (!init_controller_->TriggerImpulse(kArcNetworkStopSignal, |
| std::vector<std::string>())) { |
| static const char msg[] = "Emitting stop-arc-network init signal failed."; |
| LOG(ERROR) << msg; |
| } |
| |
| dbus_emitter_->EmitSignalWithBool(kArcInstanceStopped, clean); |
| } |
| |
| } // namespace login_manager |