blob: a8cb7e9b39cc686234c99195e0c2180756aee7ec [file] [log] [blame]
// Copyright (c) 2011 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_service.h"
#include <dbus/dbus-glib-lowlevel.h>
#include <errno.h>
#include <glib.h>
#include <grp.h>
#include <secder.h>
#include <signal.h>
#include <stdio.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "base/scoped_ptr.h"
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "chromeos/dbus/dbus.h"
#include "chromeos/dbus/service_constants.h"
#include "metrics/bootstat.h"
#include "login_manager/bindings/device_management_backend.pb.h"
#include "login_manager/child_job.h"
#include "login_manager/interface.h"
#include "login_manager/nss_util.h"
#include "login_manager/owner_key.h"
// Forcibly namespace the dbus-bindings generated server bindings instead of
// modifying the files afterward.
namespace login_manager { // NOLINT
namespace gobject { // NOLINT
#include "login_manager/bindings/server.h"
} // namespace gobject
} // namespace login_manager
namespace em = enterprise_management;
namespace login_manager {
using std::make_pair;
using std::pair;
using std::string;
using std::vector;
// Jacked from chrome base/eintr_wrapper.h
#define HANDLE_EINTR(x) ({ \
typeof(x) __eintr_result__; \
do { \
__eintr_result__ = x; \
} while (__eintr_result__ == -1 && errno == EINTR); \
__eintr_result__;\
})
int g_shutdown_pipe_write_fd = -1;
int g_shutdown_pipe_read_fd = -1;
// static
// Common code between SIG{HUP, INT, TERM}Handler.
void SessionManagerService::GracefulShutdownHandler(int signal) {
// Reinstall the default handler. We had one shot at graceful shutdown.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
RAW_CHECK(sigaction(signal, &action, NULL) == 0);
RAW_CHECK(g_shutdown_pipe_write_fd != -1);
RAW_CHECK(g_shutdown_pipe_read_fd != -1);
size_t bytes_written = 0;
do {
int rv = HANDLE_EINTR(
write(g_shutdown_pipe_write_fd,
reinterpret_cast<const char*>(&signal) + bytes_written,
sizeof(signal) - bytes_written));
RAW_CHECK(rv >= 0);
bytes_written += rv;
} while (bytes_written < sizeof(signal));
RAW_LOG(INFO,
"Successfully wrote to shutdown pipe, resetting signal handler.");
}
// static
void SessionManagerService::SIGHUPHandler(int signal) {
RAW_CHECK(signal == SIGHUP);
RAW_LOG(INFO, "Handling SIGHUP.");
GracefulShutdownHandler(signal);
}
// static
void SessionManagerService::SIGINTHandler(int signal) {
RAW_CHECK(signal == SIGINT);
RAW_LOG(INFO, "Handling SIGINT.");
GracefulShutdownHandler(signal);
}
// static
void SessionManagerService::SIGTERMHandler(int signal) {
RAW_CHECK(signal == SIGTERM);
RAW_LOG(INFO, "Handling SIGTERM.");
GracefulShutdownHandler(signal);
}
// static
const uint32 SessionManagerService::kMaxEmailSize = 200;
// static
const char SessionManagerService::kEmailSeparator = '@';
// static
const char SessionManagerService::kLegalCharacters[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
".@1234567890-+_";
// static
const char SessionManagerService::kIncognitoUser[] = "";
// static
const char SessionManagerService::kDeviceOwnerPref[] = "cros.device.owner";
// static
const char SessionManagerService::kTestingChannelFlag[] =
"--testing-channel=NamedTestingInterface:";
// static
const char SessionManagerService::kIOThreadName[] = "ThreadForIO";
// static
const char SessionManagerService::kKeygenExecutable[] = "/sbin/keygen";
// static
const char SessionManagerService::kTemporaryKeyFilename[] = "key.pub";
namespace {
// Time we wait for child job to die (in seconds).
const int kKillTimeout = 3;
const int kMaxArgumentsSize = 1024;
} // namespace
SessionManagerService::SessionManagerService(
std::vector<ChildJobInterface*> child_jobs)
: child_jobs_(child_jobs.begin(), child_jobs.end()),
child_pids_(child_jobs.size(), -1),
exit_on_child_done_(false),
keygen_job_(NULL),
session_manager_(NULL),
main_loop_(g_main_loop_new(NULL, FALSE)),
system_(new SystemUtils),
policy_(new DevicePolicy(FilePath(DevicePolicy::kDefaultPath))),
nss_(NssUtil::Create()),
key_(new OwnerKey(nss_->GetOwnerKeyFilePath())),
store_(new PrefStore(FilePath(PrefStore::kDefaultPath))),
upstart_signal_emitter_(new UpstartSignalEmitter),
session_started_(false),
io_thread_(kIOThreadName),
dont_use_directly_(new MessageLoopForUI),
message_loop_(base::MessageLoopProxy::CreateForCurrentThread()),
screen_locked_(false),
set_uid_(false),
shutting_down_(false) {
io_thread_.Start();
memset(signals_, 0, sizeof(signals_));
SetupHandlers();
}
SessionManagerService::~SessionManagerService() {
if (main_loop_)
g_main_loop_unref(main_loop_);
if (session_manager_)
g_object_unref(session_manager_);
// Remove this in case it was added by StopSession().
g_idle_remove_by_data(this);
// Remove this in case it was added by SetOwnerKey().
g_idle_remove_by_data(key_.get());
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
CHECK(sigaction(SIGALRM, &action, NULL) == 0);
CHECK(sigaction(SIGTERM, &action, NULL) == 0);
CHECK(sigaction(SIGINT, &action, NULL) == 0);
CHECK(sigaction(SIGHUP, &action, NULL) == 0);
STLDeleteElements(&child_jobs_);
}
bool SessionManagerService::Initialize() {
// Install the type-info for the service with dbus.
dbus_g_object_type_install_info(
gobject::session_manager_get_type(),
&gobject::dbus_glib_session_manager_object_info);
// Creates D-Bus GLib signal ids.
signals_[kSignalSessionStateChanged] =
g_signal_new("session_state_changed",
gobject::session_manager_get_type(),
G_SIGNAL_RUN_LAST,
0, // class offset
NULL, NULL, // accumulator and data
// TODO: This is wrong. If you need to use it at some point
// (probably only if you need to listen to this GLib signal
// instead of to the D-Bus signal), you should generate an
// appropriate marshaller that takes two string arguments.
// See e.g. http://goo.gl/vEGT4.
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, // return type
2, // num params
G_TYPE_STRING, // state ("started" or "stopped")
G_TYPE_STRING); // current user
LOG(INFO) << "SessionManagerService starting";
if (!store_->LoadOrCreate())
LOG(ERROR) << "Could not load existing settings. Continuing anyway...";
if (!policy_->LoadOrCreate())
LOG(ERROR) << "Could not load existing policy. Continuing anyway...";
return Reset();
}
bool SessionManagerService::Register(
const chromeos::dbus::BusConnection &connection) {
if (!chromeos::dbus::AbstractDbusService::Register(connection))
return false;
const std::string filter =
StringPrintf("type='method_call', interface='%s'", service_interface());
DBusConnection* conn =
::dbus_g_connection_get_connection(connection.g_connection());
CHECK(conn);
DBusError error;
::dbus_error_init(&error);
::dbus_bus_add_match(conn, filter.c_str(), &error);
if (::dbus_error_is_set(&error)) {
LOG(WARNING) << "Failed to add match to bus: " << error.name << ", message="
<< (error.message ? error.message : "unknown error");
return false;
}
if (!::dbus_connection_add_filter(conn,
&SessionManagerService::FilterMessage,
this,
NULL)) {
LOG(WARNING) << "Failed to add filter to connection";
return false;
}
return true;
}
bool SessionManagerService::Reset() {
if (session_manager_)
g_object_unref(session_manager_);
session_manager_ =
reinterpret_cast<gobject::SessionManager*>(
g_object_new(gobject::session_manager_get_type(), NULL));
// Allow references to this instance.
session_manager_->service = this;
if (main_loop_)
g_main_loop_unref(main_loop_);
main_loop_ = g_main_loop_new(NULL, false);
if (!main_loop_) {
LOG(ERROR) << "Failed to create main loop";
return false;
}
dont_use_directly_.reset(NULL);
dont_use_directly_.reset(new MessageLoopForUI);
message_loop_ = base::MessageLoopProxy::CreateForCurrentThread();
return true;
}
bool SessionManagerService::Run() {
if (!main_loop_) {
LOG(ERROR) << "You must have a main loop to call Run.";
return false;
}
int pipefd[2];
int ret = pipe(pipefd);
if (ret < 0) {
PLOG(DFATAL) << "Failed to create pipe";
} else {
g_shutdown_pipe_read_fd = pipefd[0];
g_shutdown_pipe_write_fd = pipefd[1];
g_io_add_watch_full(g_io_channel_unix_new(g_shutdown_pipe_read_fd),
G_PRIORITY_HIGH_IDLE,
GIOCondition(G_IO_IN | G_IO_PRI | G_IO_HUP),
HandleKill,
this,
NULL);
}
if (ShouldRunChildren()) {
RunChildren();
} else {
AllowGracefulExit();
}
// TODO(cmasone): A corrupted owner key means that the user needs to go
// to recovery mode. How to tell them that from here?
CHECK(key_->PopulateFromDiskIfPossible());
MessageLoop::current()->Run();
CleanupChildren(kKillTimeout);
return true;
}
bool SessionManagerService::ShouldRunChildren() {
return !file_checker_.get() || !file_checker_->exists();
}
bool SessionManagerService::ShouldStopChild(ChildJobInterface* child_job) {
return child_job->ShouldStop();
}
bool SessionManagerService::Shutdown() {
DeregisterChildWatchers();
if (session_started_) {
DLOG(INFO) << "emitting D-Bus signal SessionStateChanged:stopped";
if (signals_[kSignalSessionStateChanged]) {
g_signal_emit(session_manager_,
signals_[kSignalSessionStateChanged],
0, "stopped", current_user_.c_str());
}
}
// Even if we haven't gotten around to processing a persist task.
base::WaitableEvent event(true, false);
io_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this,
&SessionManagerService::PersistAllSync,
&event));
event.Wait();
io_thread_.Stop();
message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
LOG(INFO) << "SessionManagerService quitting run loop";
return true;
}
void SessionManagerService::RunChildren() {
bootstat_log("chrome-exec");
for (size_t i_child = 0; i_child < child_jobs_.size(); ++i_child) {
ChildJobInterface* child_job = child_jobs_[i_child];
LOG(INFO) << "Running child " << child_job->GetName() << "...";
child_pids_[i_child] = RunChild(child_job);
}
}
int SessionManagerService::RunChild(ChildJobInterface* child_job) {
child_job->RecordTime();
int pid = fork();
if (pid == 0) {
child_job->Run();
exit(1); // Run() is not supposed to return.
}
// TODO(cmasone): Remove the watcher when the child exits.
child_watchers_.push_back(g_child_watch_add_full(G_PRIORITY_HIGH_IDLE,
pid,
HandleChildExit,
this,
NULL));
return pid;
}
void SessionManagerService::KillChild(const ChildJobInterface* child_job,
int child_pid) {
uid_t to_kill_as = getuid();
if (child_job->IsDesiredUidSet())
to_kill_as = child_job->GetDesiredUid();
system_->kill(-child_pid, to_kill_as, SIGKILL);
}
bool SessionManagerService::IsKnownChild(int pid) {
return std::find(child_pids_.begin(), child_pids_.end(), pid) !=
child_pids_.end();
}
void SessionManagerService::AllowGracefulExit() {
shutting_down_ = true;
if (exit_on_child_done_) {
LOG(INFO) << "SessionManagerService set to exit on child done";
message_loop_->PostTask(
FROM_HERE,
NewRunnableMethod(this, &SessionManagerService::Shutdown));
}
}
///////////////////////////////////////////////////////////////////////////////
// SessionManagerService commands
gboolean SessionManagerService::EmitLoginPromptReady(gboolean* OUT_emitted,
GError** error) {
bootstat_log("login-prompt-ready");
// TODO(derat): Stop emitting this signal once no one's listening for it.
// Jobs that want to run after we're done booting should wait for
// login-prompt-visible or boot-complete.
*OUT_emitted =
upstart_signal_emitter_->EmitSignal("login-prompt-ready", "", error);
return *OUT_emitted;
}
gboolean SessionManagerService::EmitLoginPromptVisible(GError** error) {
bootstat_log("login-prompt-visible");
return upstart_signal_emitter_->EmitSignal("login-prompt-visible", "", error);
}
gboolean SessionManagerService::EnableChromeTesting(gboolean force_relaunch,
gchar** extra_arguments,
gchar** OUT_filepath,
GError** error) {
// Check to see if we already have Chrome testing enabled.
bool already_enabled = !chrome_testing_path_.empty();
if (!already_enabled) {
// Create a write-only temporary directory to put the testing channel in.
FilePath temp_dir_path;
if (!file_util::CreateNewTempDirectory(
FILE_PATH_LITERAL(""), &temp_dir_path))
return FALSE;
if (chmod(temp_dir_path.value().c_str(), 0003))
return FALSE;
// Get a temporary filename in the temporary directory.
char* temp_path = tempnam(temp_dir_path.value().c_str(), "");
if (!temp_path)
return FALSE;
chrome_testing_path_ = temp_path;
free(temp_path);
}
*OUT_filepath = g_strdup(chrome_testing_path_.c_str());
if (already_enabled && !force_relaunch)
return TRUE;
// Delete testing channel file if it already exists.
file_util::Delete(FilePath(chrome_testing_path_), false);
for (size_t i_child = 0; i_child < child_jobs_.size(); ++i_child) {
ChildJobInterface* child_job = child_jobs_[i_child];
if (child_job->GetName() != "chrome")
continue;
// Kill Chrome.
KillChild(child_job, child_pids_[i_child]);
std::vector<std::string> extra_argument_vector;
// Create extra argument vector.
while (*extra_arguments != NULL) {
extra_argument_vector.push_back(*extra_arguments);
++extra_arguments;
}
// Add testing channel argument to extra arguments.
std::string testing_argument = kTestingChannelFlag;
testing_argument.append(chrome_testing_path_);
extra_argument_vector.push_back(testing_argument);
// Add extra arguments to Chrome.
child_job->SetExtraArguments(extra_argument_vector);
// Run Chrome.
child_pids_[i_child] = RunChild(child_job);
return TRUE;
}
return FALSE;
}
gboolean SessionManagerService::StartSession(gchar* email_address,
gchar* unique_identifier,
gboolean* OUT_done,
GError** error) {
if (session_started_) {
const char msg[] = "Can't start session while session is already active.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_SESSION_EXISTS, msg);
return *OUT_done = FALSE;
}
if (!ValidateAndCacheUserEmail(email_address, error)) {
*OUT_done = FALSE;
return FALSE;
}
// If the current user is the owner, and isn't whitelisted or set
// as the cros.device.owner pref, then do so.
bool can_access_key = CurrentUserHasOwnerKey(key_->public_key_der(), error);
if (can_access_key) {
StoreOwnerProperties(NULL);
policy_->StoreOwnerProperties(key_.get(), current_user_, NULL);
}
// Now, the flip side...if we believe the current user to be the owner
// based on the cros.owner.device setting, and he DOESN'T have the private
// half of the public key, we must mitigate.
if ((CurrentUserIsOwner() || policy_->CurrentUserIsOwner(current_user_)) &&
!can_access_key) {
if (!(*OUT_done = mitigator_->Mitigate()))
return FALSE;
}
*OUT_done =
upstart_signal_emitter_->EmitSignal(
"start-user-session",
StringPrintf("CHROMEOS_USER=%s", current_user_.c_str()),
error);
if (*OUT_done) {
for (size_t i_child = 0; i_child < child_jobs_.size(); ++i_child) {
ChildJobInterface* child_job = child_jobs_[i_child];
child_job->StartSession(current_user_);
}
session_started_ = true;
DLOG(INFO) << "emitting D-Bus signal SessionStateChanged:started";
if (signals_[kSignalSessionStateChanged]) {
g_signal_emit(session_manager_,
signals_[kSignalSessionStateChanged],
0, "started", current_user_.c_str());
}
if (key_->HaveCheckedDisk() && !key_->IsPopulated() &&
current_user_ != kIncognitoUser) {
StartKeyGeneration();
}
}
return *OUT_done;
}
void SessionManagerService::HandleKeygenExit(GPid pid,
gint status,
gpointer data) {
SessionManagerService* manager = static_cast<SessionManagerService*>(data);
int i_child = manager->FindChildByPid(pid);
manager->child_pids_[i_child] = -1;
delete *(manager->child_jobs_.erase(manager->child_jobs_.begin() + i_child));
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
std::string key;
file_util::ReadFileToString(
FilePath(file_util::GetHomeDir().AppendASCII(kTemporaryKeyFilename)),
&key);
manager->ValidateAndStoreOwnerKey(key);
} else {
if (WIFSIGNALED(status))
LOG(ERROR) << "keygen exited on signal " << WTERMSIG(status);
else
LOG(ERROR) << "keygen exited with exit code " << WEXITSTATUS(status);
}
}
void SessionManagerService::ValidateAndStoreOwnerKey(const std::string& buf) {
std::vector<uint8> pub_key;
NssUtil::BlobFromBuffer(buf, &pub_key);
if (!CurrentUserHasOwnerKey(pub_key, NULL)) {
SendSignal(chromium::kOwnerKeySetSignal, false);
return;
}
if (!key_->PopulateFromBuffer(pub_key)) {
SendSignal(chromium::kOwnerKeySetSignal, false);
return;
}
io_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::PersistKey));
StoreOwnerProperties(NULL);
if (policy_->StoreOwnerProperties(key_.get(), current_user_, NULL)) {
io_thread_.message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &SessionManagerService::PersistPolicy));
} else {
LOG(WARNING) << "Could not immediately store owner properties in policy";
}
}
void SessionManagerService::StartKeyGeneration() {
if (!keygen_job_.get()) {
LOG(INFO) << "Creating keygen job";
std::vector<std::string> keygen_argv;
keygen_argv.push_back(kKeygenExecutable);
keygen_argv.push_back(
file_util::GetHomeDir().AppendASCII(kTemporaryKeyFilename).value());
keygen_job_.reset(new ChildJob(keygen_argv));
}
if (set_uid_)
keygen_job_->SetDesiredUid(uid_);
int pid = key_->StartGeneration(keygen_job_.get());
g_child_watch_add_full(G_PRIORITY_HIGH_IDLE,
pid,
HandleKeygenExit,
this,
NULL);
child_jobs_.push_back(keygen_job_.release());
child_pids_.push_back(pid);
}
gboolean SessionManagerService::StopSession(gchar* unique_identifier,
gboolean* OUT_done,
GError** error) {
// 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 interface.cc for that call.
LOG(INFO) << "SessionManagerService StopSession";
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
ServiceShutdown,
this,
NULL);
DeregisterChildWatchers();
// TODO(cmasone): re-enable these when we try to enable logout without exiting
// the session manager
// child_job_->StopSession();
// session_started_ = false;
return *OUT_done = TRUE;
}
gboolean SessionManagerService::SetOwnerKey(GArray* public_key_der,
GError** error) {
const char msg[] = "The session_manager now sets the Owner's public key.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ILLEGAL_PUBKEY, msg);
// Just to be safe, send back a nACK in addition to returning an error.
SendSignal(chromium::kOwnerKeySetSignal, false);
return FALSE;
}
gboolean SessionManagerService::Unwhitelist(gchar* email_address,
GArray* signature,
GError** error) {
LOG(INFO) << "Unwhitelisting " << email_address;
SessionManagerService::SigReturnCode verify_result =
VerifyHelperArray(email_address, signature);
if (verify_result == NO_KEY) {
const char msg[] = "Attempt to unwhitelist before owner's key is set.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_NO_OWNER_KEY, msg);
return FALSE;
} else if (verify_result == SIGNATURE_FAIL) {
const char msg[] = "Signature could not be verified in Unwhitelist.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_VERIFY_FAIL, msg);
return FALSE;
}
store_->Unwhitelist(email_address);
io_thread_.message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &SessionManagerService::PersistWhitelist));
return TRUE;
}
gboolean SessionManagerService::CheckWhitelist(gchar* email_address,
GArray** OUT_signature,
GError** error) {
std::string encoded;
if (!store_->GetFromWhitelist(email_address, &encoded)) {
const char msg[] = "The user is not whitelisted.";
LOG(INFO) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ILLEGAL_USER, msg);
return FALSE;
}
std::string decoded;
if (!base::Base64Decode(encoded, &decoded)) {
const char msg[] = "Signature could not be decoded in CheckWhitelist.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_DECODE_FAIL, msg);
return FALSE;
}
*OUT_signature = g_array_sized_new(FALSE, FALSE, 1, decoded.length());
g_array_append_vals(*OUT_signature, decoded.c_str(), decoded.length());
return TRUE;
}
gboolean SessionManagerService::EnumerateWhitelisted(gchar*** OUT_whitelist,
GError** error) {
std::vector<std::string> the_whitelisted;
store_->EnumerateWhitelisted(&the_whitelisted);
uint num_whitelisted = the_whitelisted.size();
*OUT_whitelist = g_new(gchar*, num_whitelisted + 1);
(*OUT_whitelist)[num_whitelisted] = NULL;
for (uint i = 0; i < num_whitelisted; ++i) {
(*OUT_whitelist)[i] = g_strndup(the_whitelisted[i].c_str(),
the_whitelisted[i].length());
}
return TRUE;
}
gboolean SessionManagerService::Whitelist(gchar* email_address,
GArray* signature,
GError** error) {
LOG(INFO) << "Whitelisting " << email_address;
SessionManagerService::SigReturnCode verify_result =
VerifyHelperArray(email_address, signature);
if (verify_result == NO_KEY) {
const char msg[] = "Attempt to whitelist before owner's key is set.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_NO_OWNER_KEY, msg);
return FALSE;
} else if (verify_result == SIGNATURE_FAIL) {
const char msg[] = "Signature could not be verified in Whitelist.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_VERIFY_FAIL, msg);
return FALSE;
}
std::string data(signature->data, signature->len);
return WhitelistHelper(email_address, data, error);
}
gboolean SessionManagerService::StoreProperty(gchar* name,
gchar* value,
GArray* signature,
GError** error) {
LOG(INFO) << "Setting pref " << name << "=" << value;
SessionManagerService::SigReturnCode verify_result =
VerifyHelperArray(base::StringPrintf("%s=%s", name, value), signature);
if (verify_result == NO_KEY) {
const char msg[] = "Attempt to store property before owner's key is set.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_NO_OWNER_KEY, msg);
return FALSE;
} else if (verify_result == SIGNATURE_FAIL) {
const char msg[] = "Signature could not be verified in StoreProperty.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_VERIFY_FAIL, msg);
return FALSE;
}
std::string data(signature->data, signature->len);
return SetPropertyHelper(name, value, data, error);
}
gboolean SessionManagerService::RetrieveProperty(gchar* name,
gchar** OUT_value,
GArray** OUT_signature,
GError** error) {
std::string value;
std::string decoded;
if (!GetPropertyHelper(name, &value, &decoded, error))
return FALSE;
*OUT_value = g_strdup_printf("%.*s", value.length(), value.c_str());
*OUT_signature = g_array_sized_new(FALSE, FALSE, 1, decoded.length());
g_array_append_vals(*OUT_signature, decoded.c_str(), decoded.length());
return TRUE;
}
void SessionManagerService::SendBooleanReply(DBusGMethodInvocation* context,
bool succeeded) {
if (context)
dbus_g_method_return(context, succeeded);
}
gboolean SessionManagerService::StorePolicy(GArray* policy_blob,
DBusGMethodInvocation* context) {
std::string policy_str(policy_blob->data, policy_blob->len);
em::PolicyFetchResponse policy;
if (!policy.ParseFromString(policy_str) ||
!policy.has_policy_data() ||
!policy.has_policy_data_signature()) {
const char msg[] = "Unable to parse policy protobuf.";
LOG(ERROR) << msg;
system_->SetAndSendGError(CHROMEOS_LOGIN_ERROR_DECODE_FAIL, context, msg);
return FALSE;
}
// Determine if the policy has pushed a new owner key and, if so, set it.
if (policy.has_new_public_key() && !key_->Equals(policy.new_public_key())) {
// The policy contains a new key, and it is different from |key_|.
std::vector<uint8> der;
nss_->BlobFromBuffer(policy.new_public_key(), &der);
if (session_started_) {
bool rotated = false;
if (policy.has_new_public_key_signature()) {
// Graceful key rotation.
std::vector<uint8> sig;
nss_->BlobFromBuffer(policy.new_public_key_signature(), &sig);
rotated = key_->Rotate(der, sig);
}
if (!rotated) {
const char msg[] = "Failed attempted key rotation!";
LOG(ERROR) << msg;
system_->SetAndSendGError(CHROMEOS_LOGIN_ERROR_ILLEGAL_PUBKEY,
context,
msg);
return FALSE;
}
} else {
// Force a new key, regardless of whether we have one or not.
if (key_->IsPopulated()) {
key_->ClobberCompromisedKey(der);
LOG(INFO) << "Clobbered existing key outside of session";
} else {
CHECK(key_->PopulateFromBuffer(der)); // Should be unable to fail.
LOG(INFO) << "Setting key outside of session";
}
}
// If here, need to persit new key to disk. Already loaded key into memory.
io_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::PersistKey));
}
// Validate signature on policy and persist to disk
const std::string& sig = policy.policy_data_signature();
SessionManagerService::SigReturnCode verify_result =
VerifyHelper(policy.policy_data(), sig.c_str(), sig.length());
if (verify_result == NO_KEY) {
NOTREACHED() << "Should have set the key earlier in this function!";
return FALSE;
} else if (verify_result == SIGNATURE_FAIL) {
const char msg[] = "Signature could not be verified in StorePolicy.";
LOG(ERROR) << msg;
system_->SetAndSendGError(CHROMEOS_LOGIN_ERROR_VERIFY_FAIL, context, msg);
return FALSE;
}
policy_->Set(policy);
io_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this,
&SessionManagerService::PersistPolicyReturn,
context));
return TRUE;
}
gboolean SessionManagerService::RetrievePolicy(GArray** OUT_policy_blob,
GError** error) {
std::string polstr;
if (!policy_->SerializeToString(&polstr)) {
const char msg[] = "Unable to serialize policy protobuf.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ENCODE_FAIL, msg);
return FALSE;
}
*OUT_policy_blob = g_array_sized_new(FALSE, FALSE, 1, polstr.length());
if (!*OUT_policy_blob) {
const char msg[] = "Unable to allocate memory for response.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_DECODE_FAIL, msg);
return FALSE;
}
g_array_append_vals(*OUT_policy_blob, polstr.c_str(), polstr.length());
return TRUE;
}
gboolean SessionManagerService::LockScreen(GError** error) {
screen_locked_ = TRUE;
system_->SendSignalToChromium(chromium::kLockScreenSignal, NULL);
LOG(INFO) << "LockScreen";
return TRUE;
}
gboolean SessionManagerService::UnlockScreen(GError** error) {
screen_locked_ = FALSE;
system_->SendSignalToChromium(chromium::kUnlockScreenSignal, NULL);
LOG(INFO) << "UnlockScreen";
return TRUE;
}
gboolean SessionManagerService::RestartJob(gint pid,
gchar* arguments,
gboolean* OUT_done,
GError** error) {
int child_pid = pid;
std::vector<int>::iterator child_pid_it =
std::find(child_pids_.begin(), child_pids_.end(), child_pid);
size_t child_index = child_pid_it - child_pids_.begin();
if (child_pid_it == child_pids_.end() ||
child_jobs_[child_index]->GetName() != "chrome") {
// If we didn't find the pid, or we don't think that job was chrome...
*OUT_done = FALSE;
const char msg[] = "Provided pid is unknown.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_UNKNOWN_PID, msg);
return FALSE;
}
// Waiting for Chrome to shutdown takes too much time.
// We're killing it immediately hoping that data Chrome uses before
// logging in is not corrupted.
// TODO(avayvod): Remove RestartJob when crosbug.com/6924 is fixed.
KillChild(child_jobs_[child_index], child_pid);
char arguments_buffer[kMaxArgumentsSize + 1];
snprintf(arguments_buffer, sizeof(arguments_buffer), "%s", arguments);
arguments_buffer[kMaxArgumentsSize] = '\0';
string arguments_string(arguments_buffer);
child_jobs_[child_index]->SetArguments(arguments_string);
child_pids_[child_index] = RunChild(child_jobs_[child_index]);
// To set "logged-in" state for BWSI mode.
return StartSession(const_cast<gchar*>(kIncognitoUser), NULL,
OUT_done, error);
}
gboolean SessionManagerService::RestartEntd(GError** error) {
LOG(INFO) << "Restarting entd.";
// Shutdown entd if it is currently running, blocking this thread and
// method call until it has finished shutting down.
int stop_status = system("/sbin/initctl stop entd");
// Stop may have failed, but it may be ok if not already running. Error
// messages will go to session manager log.
LOG_IF(INFO, stop_status != 0)
<< "Could not stop entd, likely was not running.";
string command = StringPrintf("/sbin/initctl start entd "
"CHROMEOS_USER=%s",
current_user_.c_str());
// Start entd with the current user passed in, blocking this thread
// and method call until it has finished starting.
bool restarted = (system(command.c_str()) == 0);
LOG(INFO) << "Restart was " << (restarted ? "" : "not ")
<< "successful.";
return restarted;
}
///////////////////////////////////////////////////////////////////////////////
// glib event handlers
void SessionManagerService::HandleChildExit(GPid pid,
gint status,
gpointer data) {
// If I could wait for descendants here, I would. Instead, I kill them.
kill(-pid, SIGKILL);
DLOG(INFO) << "Handling child process exit.";
if (WIFSIGNALED(status)) {
DLOG(INFO) << " Exited with signal " << WTERMSIG(status);
} else if (WIFEXITED(status)) {
DLOG(INFO) << " Exited with exit code " << WEXITSTATUS(status);
CHECK(WEXITSTATUS(status) != ChildJobInterface::kCantSetUid);
CHECK(WEXITSTATUS(status) != ChildJobInterface::kCantExec);
} else {
DLOG(INFO) << " Exited...somehow, without an exit code or a signal??";
}
// If the child _ever_ exits uncleanly, we want to start it up again.
SessionManagerService* manager = static_cast<SessionManagerService*>(data);
// Do nothing if already shutting down.
if (manager->shutting_down_)
return;
ChildJobInterface* child_job = NULL;
int i_child = manager->FindChildByPid(pid);
if (i_child >= 0) {
child_job = manager->child_jobs_[i_child];
manager->child_pids_[i_child] = -1;
}
LOG(ERROR) << StringPrintf(
"Process %s(%d) exited.",
child_job ? child_job->GetName().c_str() : "",
pid);
if (manager->screen_locked_) {
LOG(ERROR) << "Screen locked, shutting down",
ServiceShutdown(data);
return;
}
if (child_job) {
if (manager->ShouldStopChild(child_job)) {
LOG(INFO) << "Child stopped, shutting down",
ServiceShutdown(data);
} else if (manager->ShouldRunChildren()) {
// TODO(cmasone): deal with fork failing in RunChild()
LOG(INFO) << StringPrintf(
"Running child %s again...", child_job->GetName().data());
manager->child_pids_[i_child] = manager->RunChild(child_job);
} else {
LOG(INFO) << StringPrintf(
"Should NOT run %s again...", child_job->GetName().data());
manager->AllowGracefulExit();
}
} else {
LOG(ERROR) << "Couldn't find pid of exiting child: " << pid;
}
}
gboolean SessionManagerService::HandleKill(GIOChannel* source,
GIOCondition condition,
gpointer data) {
// We only get called if there's data on the pipe. If there's data, we're
// supposed to exit. So, don't even bother to read it.
LOG(INFO) << "SessionManagerService - data on pipe, so exiting";
return ServiceShutdown(data);
}
gboolean SessionManagerService::ServiceShutdown(gpointer data) {
SessionManagerService* manager = static_cast<SessionManagerService*>(data);
manager->Shutdown();
LOG(INFO) << "SessionManagerService exiting";
return FALSE; // So that the event source that called this gets removed.
}
void SessionManagerService::PersistKey() {
LOG(INFO) << "Persisting Owner key to disk.";
bool what_happened = key_->Persist();
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::SendSignal,
chromium::kOwnerKeySetSignal,
what_happened));
}
void SessionManagerService::PersistAllSync(base::WaitableEvent* event) {
store_->Persist();
policy_->Persist();
LOG(INFO) << "Persisted store, policy to disk.";
event->Signal();
}
void SessionManagerService::PersistStore() {
LOG(INFO) << "Persisting Store to disk.";
bool what_happened = store_->Persist();
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::SendSignal,
chromium::kPropertyChangeCompleteSignal,
what_happened));
}
void SessionManagerService::PersistPolicyReturn(DBusGMethodInvocation* ctxt) {
LOG(INFO) << "Persisting policy to disk.";
bool what_happened = policy_->Persist();
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this,
&SessionManagerService::SendBooleanReply,
ctxt,
what_happened));
}
void SessionManagerService::PersistPolicy() {
LOG(INFO) << "Persisting policy to disk.";
bool what_happened = policy_->Persist();
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::SendSignal,
chromium::kPropertyChangeCompleteSignal,
what_happened));
}
void SessionManagerService::PersistWhitelist() {
LOG(INFO) << "Persisting Whitelist to disk.";
bool what_happened = store_->Persist();
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::SendSignal,
chromium::kWhitelistChangeCompleteSignal,
what_happened));
}
///////////////////////////////////////////////////////////////////////////////
// Utility Methods
// This can probably be more efficient, if it needs to be.
// static
bool SessionManagerService::ValidateEmail(const string& email_address) {
if (email_address.find_first_not_of(kLegalCharacters) != string::npos)
return false;
size_t at = email_address.find(kEmailSeparator);
// it has NO @.
if (at == string::npos)
return false;
// it has more than one @.
if (email_address.find(kEmailSeparator, at+1) != string::npos)
return false;
return true;
}
// static
DBusHandlerResult SessionManagerService::FilterMessage(DBusConnection* conn,
DBusMessage* message,
void* data) {
SessionManagerService* service = static_cast<SessionManagerService*>(data);
if (::dbus_message_is_method_call(message,
service->service_interface(),
kSessionManagerRestartJob)) {
const char* sender = ::dbus_message_get_sender(message);
if (!sender) {
LOG(ERROR) << "Call to RestartJob has no sender";
return DBUS_HANDLER_RESULT_HANDLED;
}
LOG(INFO) << "Received RestartJob from " << sender;
DBusMessage* get_pid =
::dbus_message_new_method_call("org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetConnectionUnixProcessID");
CHECK(get_pid);
::dbus_message_append_args(get_pid,
DBUS_TYPE_STRING, &sender,
DBUS_TYPE_INVALID);
DBusMessage* got_pid =
::dbus_connection_send_with_reply_and_block(conn, get_pid, -1, NULL);
::dbus_message_unref(get_pid);
if (!got_pid) {
LOG(ERROR) << "Could not look up sender of RestartJob";
return DBUS_HANDLER_RESULT_HANDLED;
}
uint32 pid;
if (!::dbus_message_get_args(got_pid, NULL,
DBUS_TYPE_UINT32, &pid,
DBUS_TYPE_INVALID)) {
::dbus_message_unref(got_pid);
LOG(ERROR) << "Could not extract pid of sender of RestartJob";
return DBUS_HANDLER_RESULT_HANDLED;
}
::dbus_message_unref(got_pid);
if (!service->IsKnownChild(pid)) {
LOG(WARNING) << "Sender of RestartJob is no child of mine!";
return DBUS_HANDLER_RESULT_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void SessionManagerService::SetupHandlers() {
// I have to ignore SIGUSR1, because Xorg sends it to this process when it's
// got no clients and is ready for new ones. If we don't ignore it, we die.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_IGN;
CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
action.sa_handler = SessionManagerService::do_nothing;
CHECK(sigaction(SIGALRM, &action, NULL) == 0);
// We need to handle SIGTERM, because that is how many POSIX-based distros ask
// processes to quit gracefully at shutdown time.
action.sa_handler = SIGTERMHandler;
CHECK(sigaction(SIGTERM, &action, NULL) == 0);
// Also handle SIGINT - when the user terminates the browser via Ctrl+C.
// If the browser process is being debugged, GDB will catch the SIGINT first.
action.sa_handler = SIGINTHandler;
CHECK(sigaction(SIGINT, &action, NULL) == 0);
// And SIGHUP, for when the terminal disappears. On shutdown, many Linux
// distros send SIGHUP, SIGTERM, and then SIGKILL.
action.sa_handler = SIGHUPHandler;
CHECK(sigaction(SIGHUP, &action, NULL) == 0);
}
gboolean SessionManagerService::CurrentUserIsOwner() {
std::string value;
std::string decoded;
if (!GetPropertyHelper(kDeviceOwnerPref, &value, &decoded, NULL))
return FALSE;
std::string was_signed = base::StringPrintf("%s=%s",
kDeviceOwnerPref,
value.c_str());
if (VerifyHelper(was_signed, decoded.c_str(), decoded.length()) != SUCCESS) {
LOG(ERROR) << "Owner pref signature could not be verified.";
return FALSE;
}
return value == current_user_;
}
gboolean SessionManagerService::CurrentUserHasOwnerKey(
const std::vector<uint8>& pub_key,
GError** error) {
if (!nss_->MightHaveKeys())
return FALSE;
if (!nss_->OpenUserDB()) {
const char msg[] = "Could not open the current user's NSS database.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_NO_USER_NSSDB, msg);
return FALSE;
}
if (!nss_->GetPrivateKey(pub_key)) {
const char msg[] = "Could not verify that public key belongs to the owner.";
LOG(WARNING) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ILLEGAL_PUBKEY, msg);
return FALSE;
}
return TRUE;
}
gboolean SessionManagerService::ValidateAndCacheUserEmail(
const gchar* email_address,
GError** error) {
// basic validity checking; avoid buffer overflows here, and
// canonicalize the email address a little.
char email[kMaxEmailSize + 1];
snprintf(email, sizeof(email), "%s", email_address);
email[kMaxEmailSize] = '\0'; // Just to be sure.
string email_string(email);
if (email_string != kIncognitoUser && !ValidateEmail(email_string)) {
const char msg[] = "Provided email address is not valid. ASCII only.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_INVALID_EMAIL, msg);
return FALSE;
}
current_user_ = StringToLowerASCII(email_string);
return TRUE;
}
int SessionManagerService::FindChildByPid(int pid) {
for (int i = 0; i < child_pids_.size(); ++i) {
if (child_pids_[i] == pid)
return i;
}
return -1;
}
void SessionManagerService::CleanupChildren(int timeout) {
vector<pair<int, uid_t> > pids_to_kill;
for (size_t i = 0; i < child_pids_.size(); ++i) {
const int pid = child_pids_[i];
if (pid < 0)
continue;
ChildJobInterface* job = child_jobs_[i];
if (job->ShouldNeverKill())
continue;
const uid_t uid = job->IsDesiredUidSet() ? job->GetDesiredUid() : getuid();
pids_to_kill.push_back(make_pair(pid, uid));
system_->kill(pid, uid, (session_started_ ? SIGTERM : SIGKILL));
}
for (vector<pair<int, uid_t> >::const_iterator it = pids_to_kill.begin();
it != pids_to_kill.end(); ++it) {
const int pid = it->first;
const uid_t uid = it->second;
if (!system_->ChildIsGone(pid, timeout))
system_->kill(pid, uid, SIGABRT);
}
}
void SessionManagerService::DeregisterChildWatchers() {
// Remove child exit handlers.
for (size_t i = 0; i < child_watchers_.size(); ++i)
g_source_remove(child_watchers_[i]);
child_watchers_.clear();
}
gboolean SessionManagerService::StoreOwnerProperties(GError** error) {
if (!SignAndStoreProperty(kDeviceOwnerPref,
current_user_,
"Could not sign owner property.",
error)) {
return FALSE;
}
return SignAndWhitelist(current_user_, "Could not whitelist owner.", error);
}
gboolean SessionManagerService::SignAndStoreProperty(const std::string& name,
const std::string& value,
const std::string& msg,
GError** error) {
std::vector<uint8> signature;
std::string to_sign = base::StringPrintf("%s=%s",
kDeviceOwnerPref,
current_user_.c_str());
const uint8* data = reinterpret_cast<const uint8*>(to_sign.c_str());
if (!key_->Sign(data, to_sign.length(), &signature)) {
LOG_IF(ERROR, error) << msg;
LOG_IF(WARNING, !error) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ILLEGAL_PUBKEY, msg.c_str());
return FALSE;
}
std::string signature_string(reinterpret_cast<const char*>(&signature[0]),
signature.size());
return SetPropertyHelper(kDeviceOwnerPref,
current_user_,
signature_string,
error);
}
gboolean SessionManagerService::SignAndWhitelist(const std::string& email,
const std::string& msg,
GError** error) {
std::vector<uint8> signature;
const uint8* data = reinterpret_cast<const uint8*>(current_user_.c_str());
if (!key_->Sign(data, current_user_.length(), &signature)) {
LOG_IF(ERROR, error) << msg;
LOG_IF(WARNING, !error) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ILLEGAL_PUBKEY, msg.c_str());
return FALSE;
}
std::string signature_string(reinterpret_cast<const char*>(&signature[0]),
signature.size());
return WhitelistHelper(current_user_, signature_string, error);
}
gboolean SessionManagerService::SetPropertyHelper(const std::string& name,
const std::string& value,
const std::string& signature,
GError** error) {
std::string encoded;
if (!base::Base64Encode(signature, &encoded)) {
const char msg[] = "Signature could not be encoded.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ENCODE_FAIL, msg);
return FALSE;
}
store_->Set(name, value, encoded);
io_thread_.message_loop()->PostTask(
FROM_HERE, NewRunnableMethod(this, &SessionManagerService::PersistStore));
return TRUE;
}
SessionManagerService::SigReturnCode
SessionManagerService::VerifyHelperArray(const std::string& data, GArray* sig) {
return VerifyHelper(data, sig->data, sig->len);
}
SessionManagerService::SigReturnCode
SessionManagerService::VerifyHelper(const std::string& data,
const char* sig,
uint32 sig_len) {
if (!key_->IsPopulated())
return NO_KEY;
if (!key_->Verify(reinterpret_cast<const uint8*>(data.c_str()),
data.length(),
reinterpret_cast<const uint8*>(sig),
sig_len)) {
return SIGNATURE_FAIL;
}
return SUCCESS;
}
gboolean SessionManagerService::WhitelistHelper(const std::string& email,
const std::string& signature,
GError** error) {
std::string encoded;
if (!base::Base64Encode(signature, &encoded)) {
const char msg[] = "Signature could not be encoded.";
LOG(ERROR) << msg;
system_->SetGError(error, CHROMEOS_LOGIN_ERROR_ENCODE_FAIL, msg);
return FALSE;
}
store_->Whitelist(email, encoded);
io_thread_.message_loop()->PostTask(
FROM_HERE,
NewRunnableMethod(this, &SessionManagerService::PersistWhitelist));
return TRUE;
}
gboolean SessionManagerService::GetPropertyHelper(const std::string& name,
std::string* OUT_value,
std::string* OUT_signature,
GError** err) {
std::string encoded;
if (!store_->Get(name, OUT_value, &encoded)) {
std::string msg =
base::StringPrintf("The requested property %s is unknown.",
name.c_str());
LOG(WARNING) << msg;
system_->SetGError(err, CHROMEOS_LOGIN_ERROR_UNKNOWN_PROPERTY, msg.c_str());
return FALSE;
}
if (!base::Base64Decode(encoded, OUT_signature)) {
const char msg[] = "Signature could not be decoded.";
LOG(ERROR) << msg;
system_->SetGError(err, CHROMEOS_LOGIN_ERROR_DECODE_FAIL, msg);
return FALSE;
}
return TRUE;
}
void SessionManagerService::SendSignal(const char signal_name[],
bool succeeded) {
system_->SendSignalToChromium(signal_name, succeeded ? "success" : "failure");
}
// static
std::vector<std::vector<std::string> > SessionManagerService::GetArgLists(
std::vector<std::string> args) {
std::vector<std::string> arg_list;
std::vector<std::vector<std::string> > arg_lists;
for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) {
if (args[i_arg] == "--") {
if (arg_list.size()) {
arg_lists.push_back(arg_list);
arg_list.clear();
}
} else {
arg_list.push_back(args[i_arg]);
}
}
if (arg_list.size()) {
arg_lists.push_back(arg_list);
}
return arg_lists;
}
} // namespace login_manager