blob: a7d567544123e9050346b496066451a817d159b2 [file] [log] [blame]
// Copyright 2019 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 "kerberos/kerberos_adaptor.h"
#include <string>
#include <unordered_set>
#include <utility>
#include <base/check.h>
#include <base/compiler_specific.h>
#include <base/files/file_util.h>
#include <base/optional.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/time/time.h>
#include <brillo/dbus/dbus_object.h>
#include <brillo/errors/error.h>
#include <dbus/login_manager/dbus-constants.h>
#include <libpasswordprovider/password_provider.h>
#include <session_manager/dbus-proxies.h>
#include "kerberos/account_manager.h"
#include "kerberos/error_strings.h"
#include "kerberos/kerberos_metrics.h"
#include "kerberos/krb5_interface.h"
#include "kerberos/krb5_interface_impl.h"
#include "kerberos/krb5_jail_wrapper.h"
#include "kerberos/platform_helper.h"
namespace kerberos {
namespace {
constexpr base::TimeDelta kTicketExpiryCheckDelay =
base::TimeDelta::FromSeconds(3);
using ByteArray = KerberosAdaptor::ByteArray;
// Serializes |proto| to a vector of bytes. CHECKs for success (should
// never fail if there are no required proto fields).
ByteArray SerializeProto(const google::protobuf::MessageLite& proto) {
ByteArray proto_blob(proto.ByteSizeLong());
CHECK(proto.SerializeToArray(proto_blob.data(), proto_blob.size()));
return proto_blob;
}
// Parses a proto from an array of bytes |proto_blob|. Returns
// ERROR_PARSE_REQUEST_FAILED on error.
WARN_UNUSED_RESULT ErrorType ParseProto(google::protobuf::MessageLite* proto,
const ByteArray& proto_blob) {
if (!proto->ParseFromArray(proto_blob.data(), proto_blob.size())) {
LOG(ERROR) << "Failed to parse proto";
return ERROR_PARSE_REQUEST_FAILED;
}
return ERROR_NONE;
}
void PrintRequest(const char* method_name) {
LOG(INFO) << ">>> " << method_name;
}
void PrintResult(const char* method_name, ErrorType error) {
if (error == ERROR_NONE)
LOG(INFO) << "<<< " << method_name << " succeeded";
else
LOG(ERROR) << "<<< " << method_name << " failed: " << GetErrorString(error);
}
// Calls Session Manager to get the user hash for the primary session. Returns
// an empty string and logs on error.
std::string GetSanitizedUsername(brillo::dbus_utils::DBusObject* dbus_object) {
std::string username;
std::string sanitized_username;
brillo::ErrorPtr error;
org::chromium::SessionManagerInterfaceProxy proxy(dbus_object->GetBus());
if (!proxy.RetrievePrimarySession(&username, &sanitized_username, &error)) {
const char* error_msg =
error ? error->GetMessage().c_str() : "Unknown error.";
LOG(ERROR) << "Call to RetrievePrimarySession failed. " << error_msg;
return std::string();
}
return sanitized_username;
}
} // namespace
KerberosAdaptor::KerberosAdaptor(
std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object)
: org::chromium::KerberosAdaptor(this),
dbus_object_(std::move(dbus_object)) {}
KerberosAdaptor::~KerberosAdaptor() = default;
void KerberosAdaptor::RegisterAsync(
const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
completion_callback) {
RegisterWithDBusObject(dbus_object_.get());
dbus_object_->RegisterAsync(completion_callback);
// Get the sanitized username (aka user hash). It's needded to determine the
// daemon store directory where account data is stored.
base::FilePath storage_dir;
if (storage_dir_for_testing_) {
storage_dir = *storage_dir_for_testing_;
} else {
const std::string sanitized_username =
GetSanitizedUsername(dbus_object_.get());
if (!sanitized_username.empty()) {
storage_dir = base::FilePath("/run/daemon-store/kerberosd/")
.Append(sanitized_username);
} else {
// /tmp is a tmpfs and the daemon is shut down on logout, so data is
// cleared on logout. Better than nothing, though.
storage_dir = base::FilePath("/tmp");
LOG(ERROR) << "Failed to retrieve user hash to determine storage "
"directory. Falling back to "
<< storage_dir.value() << ".";
}
}
// Might have already been set for testing.
if (!metrics_)
metrics_ = std::make_unique<KerberosMetrics>(storage_dir);
// Create krb5 or use the one given for testing.
auto krb5 = krb5_for_testing_ ? std::move(krb5_for_testing_)
: std::make_unique<Krb5JailWrapper>(
std::make_unique<Krb5InterfaceImpl>());
manager_ = std::make_unique<AccountManager>(
storage_dir,
base::BindRepeating(&KerberosAdaptor::OnKerberosFilesChanged,
base::Unretained(this)),
base::BindRepeating(&KerberosAdaptor::OnKerberosTicketExpiring,
base::Unretained(this)),
std::move(krb5), std::make_unique<password_provider::PasswordProvider>(),
metrics_.get());
manager_->LoadAccounts();
// Wait a little before calling StartObservingTickets. Apparently, signals
// are not quite wired up properly at this point. If signals are emitted here,
// they never reach Chrome, even if Chrome made sure it connected to the
// signal.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindRepeating(&KerberosAdaptor::StartObservingTickets,
weak_ptr_factory_.GetWeakPtr()),
kTicketExpiryCheckDelay);
}
ByteArray KerberosAdaptor::AddAccount(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
AddAccountRequest request;
ErrorType error = ParseProto(&request, request_blob);
if (error == ERROR_NONE) {
error =
manager_->AddAccount(request.principal_name(), request.is_managed());
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
AddAccountResponse response;
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::RemoveAccount(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
RemoveAccountRequest request;
ErrorType error = ParseProto(&request, request_blob);
RemoveAccountResponse response;
if (error == ERROR_NONE) {
error = manager_->RemoveAccount(request.principal_name());
GetAccountsList(response.mutable_accounts());
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::ClearAccounts(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
ClearAccountsRequest request;
ErrorType error = ParseProto(&request, request_blob);
ClearAccountsResponse response;
if (error == ERROR_NONE) {
std::unordered_set<std::string> keep_list(
request.principal_names_to_ignore_size());
for (int n = 0; n < request.principal_names_to_ignore_size(); ++n)
keep_list.insert(request.principal_names_to_ignore(n));
error = manager_->ClearAccounts(request.mode(), std::move(keep_list));
GetAccountsList(response.mutable_accounts());
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::ListAccounts(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
ListAccountsRequest request;
ErrorType error = ParseProto(&request, request_blob);
// Note: request is empty right now, but keeping it for future changes.
std::vector<Account> accounts;
ListAccountsResponse response;
if (error == ERROR_NONE)
GetAccountsList(response.mutable_accounts());
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::SetConfig(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
SetConfigRequest request;
ErrorType error = ParseProto(&request, request_blob);
if (error == ERROR_NONE)
error = manager_->SetConfig(request.principal_name(), request.krb5conf());
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
SetConfigResponse response;
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::ValidateConfig(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
ValidateConfigRequest request;
ErrorType error = ParseProto(&request, request_blob);
ConfigErrorInfo error_info;
if (error == ERROR_NONE) {
error = manager_->ValidateConfig(request.krb5conf(), &error_info);
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
metrics_->ReportValidateConfigErrorCode(error_info.code());
ValidateConfigResponse response;
response.set_error(error);
*response.mutable_error_info() = error_info;
return SerializeProto(response);
}
ByteArray KerberosAdaptor::AcquireKerberosTgt(
const ByteArray& request_blob, const base::ScopedFD& password_fd) {
PrintRequest(__FUNCTION__);
AcquireKerberosTgtRequest request;
ErrorType error = ParseProto(&request, request_blob);
base::Optional<std::string> password;
if (error == ERROR_NONE) {
password = ReadPipeToString(password_fd.get());
if (!password.has_value()) {
LOG(ERROR) << "Failed to read password pipe";
error = ERROR_LOCAL_IO;
}
}
if (error == ERROR_NONE) {
metrics_->StartAcquireTgtTimer();
error = manager_->AcquireTgt(request.principal_name(), password.value(),
request.remember_password(),
request.use_login_password());
metrics_->StopAcquireTgtTimerAndReport();
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
AcquireKerberosTgtResponse response;
response.set_error(error);
return SerializeProto(response);
}
ByteArray KerberosAdaptor::GetKerberosFiles(const ByteArray& request_blob) {
PrintRequest(__FUNCTION__);
GetKerberosFilesRequest request;
ErrorType error = ParseProto(&request, request_blob);
GetKerberosFilesResponse response;
if (error == ERROR_NONE) {
error = manager_->GetKerberosFiles(request.principal_name(),
response.mutable_files());
}
PrintResult(__FUNCTION__, error);
metrics_->ReportDBusCallResult(__FUNCTION__, error);
response.set_error(error);
return SerializeProto(response);
}
void KerberosAdaptor::set_storage_dir_for_testing(const base::FilePath& dir) {
DCHECK(!manager_);
storage_dir_for_testing_ = dir;
}
void KerberosAdaptor::set_krb5_for_testing(
std::unique_ptr<Krb5Interface> krb5) {
DCHECK(!manager_);
krb5_for_testing_ = std::move(krb5);
}
void KerberosAdaptor::set_metrics_for_testing(
std::unique_ptr<KerberosMetrics> metrics) {
DCHECK(!manager_);
metrics_ = std::move(metrics);
}
void KerberosAdaptor::StartObservingTickets() {
manager_->StartObservingTickets();
}
void KerberosAdaptor::OnKerberosFilesChanged(
const std::string& principal_name) {
LOG(INFO) << "Firing signal KerberosFilesChanged";
SendKerberosFilesChangedSignal(principal_name);
}
void KerberosAdaptor::OnKerberosTicketExpiring(
const std::string& principal_name) {
LOG(INFO) << "Firing signal KerberosTicketExpiring";
SendKerberosTicketExpiringSignal(principal_name);
}
void KerberosAdaptor::GetAccountsList(RepeatedAccountField* repeated_accounts) {
std::vector<Account> accounts = manager_->ListAccounts();
for (const auto& account : accounts)
*repeated_accounts->Add() = account;
}
} // namespace kerberos