blob: d9f4d328cdcb8c17c96c6082c112562e58e7448d [file] [log] [blame]
// 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 <string>
#include <base/basictypes.h>
#include <base/bind.h>
#include <base/callback.h>
#include <base/command_line.h>
#include <base/file_util.h>
#include <base/memory/ref_counted.h>
#include <base/memory/scoped_ptr.h>
#include <base/message_loop/message_loop.h>
#include <base/message_loop/message_loop_proxy.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <chromeos/cryptohome.h>
#include <chromeos/utility.h>
#include <crypto/scoped_nss_types.h>
#include <dbus/message.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_management_backend.pb.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"
using base::FilePath;
using chromeos::cryptohome::home::kGuestUserName;
using chromeos::cryptohome::home::GetUserPath;
using chromeos::cryptohome::home::SanitizeUserName;
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";
namespace {
// Constants used in email validation.
const char kEmailSeparator = '@';
const char kLegalCharacters[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
".@1234567890-+_";
// 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");
} // 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,
scoped_ptr<PolicyService> policy_service)
: username(username),
userhash(userhash),
is_incognito(is_incognito),
slot(slot.Pass()),
policy_service(policy_service.Pass()) {
}
~UserSession() {}
const std::string username;
const std::string userhash;
const bool is_incognito;
crypto::ScopedPK11Slot slot;
scoped_ptr<PolicyService> policy_service;
};
SessionManagerImpl::SessionManagerImpl(
scoped_ptr<UpstartSignalEmitter> emitter,
DBusSignalEmitterInterface* dbus_emitter,
base::Closure lock_screen_closure,
base::Closure restart_device_closure,
KeyGenerator* key_gen,
ProcessManagerServiceInterface* manager,
LoginMetrics* metrics,
NssUtil* nss,
SystemUtils* utils)
: session_started_(false),
session_stopping_(false),
screen_locked_(false),
upstart_signal_emitter_(emitter.Pass()),
lock_screen_closure_(lock_screen_closure),
restart_device_closure_(restart_device_closure),
dbus_emitter_(dbus_emitter),
key_gen_(key_gen),
manager_(manager),
login_metrics_(metrics),
nss_(nss),
system_(utils),
owner_key_(nss->GetOwnerKeyFilePath(), nss),
mitigator_(key_gen) {
}
SessionManagerImpl::~SessionManagerImpl() {
STLDeleteValues(&user_sessions_);
device_policy_->set_delegate(NULL); // Could use WeakPtr instead?
}
void SessionManagerImpl::InjectPolicyServices(
scoped_ptr<DevicePolicyService> device_policy,
scoped_ptr<UserPolicyServiceFactory> user_policy_factory,
scoped_ptr<DeviceLocalAccountPolicyService> device_local_account_policy) {
device_policy_ = device_policy.Pass();
user_policy_factory_ = user_policy_factory.Pass();
device_local_account_policy_ = device_local_account_policy.Pass();
}
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::Initialize() {
scoped_refptr<base::MessageLoopProxy> loop_proxy =
base::MessageLoop::current()->message_loop_proxy();
key_gen_->set_delegate(this);
device_policy_.reset(DevicePolicyService::Create(login_metrics_,
&owner_key_,
&mitigator_,
nss_,
loop_proxy));
device_policy_->set_delegate(this);
user_policy_factory_.reset(new UserPolicyServiceFactory(getuid(),
loop_proxy,
nss_,
system_));
device_local_account_policy_.reset(
new DeviceLocalAccountPolicyService(
base::FilePath(kDeviceLocalAccountStateDir),
&owner_key_,
loop_proxy));
if (device_policy_->Initialize()) {
device_local_account_policy_->UpdateDeviceSettings(
device_policy_->GetSettings());
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();
}
}
void SessionManagerImpl::EmitLoginPromptVisible(Error* error) {
login_metrics_->RecordStats("login-prompt-visible");
dbus_emitter_->EmitSignal(kLoginPromptVisibleSignal);
scoped_ptr<dbus::Response> emit_response =
upstart_signal_emitter_->EmitSignal("login-prompt-visible",
std::vector<std::string>());
if (!emit_response) {
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& email,
const std::string& unique_id,
Error* error) {
// Validate the |email|.
const std::string email_string(StringToLowerASCII(email));
const bool is_incognito = ((email_string == kGuestUserName) ||
(email_string == kDemoUser));
if (!is_incognito && !ValidateEmail(email_string)) {
const char msg[] = "Provided email address is not valid. ASCII only.";
LOG(ERROR) << msg;
error->Set(dbus_error::kInvalidAccount, msg);
return false;
}
// Check if this user already started a session.
if (user_sessions_.count(email_string) > 0) {
const char msg[] = "Provided email address already started a session.";
LOG(ERROR) << msg;
error->Set(dbus_error::kSessionExists, msg);
return false;
}
// Create a UserSession object for this user.
std::string error_name;
scoped_ptr<UserSession> user_session(CreateUserSession(email_string,
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> emit_response =
upstart_signal_emitter_->EmitSignal(
"start-user-session",
std::vector<std::string>(1, "CHROMEOS_USER=" + email_string));
if (!emit_response) {
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(email_string, user_session->userhash);
session_started_ = true;
user_sessions_[email_string] = 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(email_string);
}
// 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 dbus_glib_shim.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* policy_blob,
size_t policy_blob_len,
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>* policy_data,
Error* error) {
if (!device_policy_->Retrieve(policy_data)) {
const char msg[] = "Failed to retrieve policy data.";
LOG(ERROR) << msg;
error->Set(dbus_error::kSigEncodeFail, msg);
return;
}
}
void SessionManagerImpl::StorePolicyForUser(
const std::string& user_email,
const uint8* policy_blob,
size_t policy_blob_len,
PolicyService::Completion* completion) {
PolicyService* policy_service = GetPolicyService(user_email);
if (!policy_service) {
PolicyService::Error error(
dbus_error::kSessionDoesNotExist,
"Cannot store user policy before session is started.");
LOG(ERROR) << error.message();
completion->ReportFailure(error);
return;
}
policy_service->Store(
policy_blob,
policy_blob_len,
completion,
PolicyService::KEY_INSTALL_NEW | PolicyService::KEY_ROTATE);
}
void SessionManagerImpl::RetrievePolicyForUser(const std::string& user_email,
std::vector<uint8>* policy_data,
Error* error) {
PolicyService* policy_service = GetPolicyService(user_email);
if (!policy_service) {
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)) {
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* policy_blob,
size_t policy_blob_len,
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>* policy_data,
Error* error) {
if (!device_local_account_policy_->Retrieve(account_id, policy_data)) {
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::LockScreen(Error* error) {
if (!session_started_) {
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()) {
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(pid_t pid,
const std::string& arguments,
Error* error) {
if (!manager_->IsBrowser(pid)) {
const char msg[] = "Provided pid is unknown.";
LOG(ERROR) << msg;
error->Set(dbus_error::kUnknownPid, msg);
return false;
}
gchar **argv = NULL;
gint argc = 0;
GError* parse_error = NULL;
if (!g_shell_parse_argv(arguments.c_str(), &argc, &argv, &parse_error)) {
LOG(ERROR) << "Could not parse command: " << parse_error->message;
g_strfreev(argv);
error->Set(DBUS_ERROR_INVALID_ARGS, parse_error->message);
return false;
}
CommandLine new_command_line(argc, argv);
g_strfreev(argv);
// To set "logged-in" state for BWSI mode.
if (!StartSession(kGuestUserName, "", error))
return false;
manager_->RestartBrowserWithArgs(new_command_line.argv(), false);
return true;
}
void SessionManagerImpl::StartDeviceWipe(Error* error) {
const base::FilePath session_path(kLoggedInFlag);
if (system_->Exists(session_path)) {
const char msg[] = "A user has already logged in this boot.";
LOG(ERROR) << msg;
error->Set(dbus_error::kSessionExists, msg);
return;
}
InitiateDeviceWipe();
}
void SessionManagerImpl::SetFlagsForUser(
const std::string& user_email,
const std::vector<std::string>& session_user_flags) {
manager_->SetFlagsForUser(user_email, session_user_flags);
}
void SessionManagerImpl::OnPolicyPersisted(bool success) {
dbus_emitter_->EmitSignalWithSuccessFailure(kPropertyChangeCompleteSignal,
success);
device_local_account_policy_->UpdateDeviceSettings(
device_policy_->GetSettings());
}
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 base::FilePath reset_path(kResetFile);
system_->AtomicFileWrite(reset_path, "fast safe");
restart_device_closure_.Run();
}
// static
bool SessionManagerImpl::ValidateEmail(const std::string& email_address) {
if (email_address.find_first_not_of(kLegalCharacters) != 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) {
scoped_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,
slot.Pass(),
user_policy.Pass());
}
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();
}
} // namespace login_manager