blob: 19f295f886a82d4aed0d3e9f0a8e8968d12447c2 [file] [log] [blame]
// Copyright 2020 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 "system-proxy/kerberos_client.h"
#include <utility>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/files/file_util.h>
#include <base/strings/stringprintf.h>
#include <dbus/kerberos/dbus-constants.h>
#include <dbus/message.h>
#include <kerberos/proto_bindings/kerberos_service.pb.h>
namespace system_proxy {
namespace {
// The kerberos files are written in the mount namespace of the System-proxy
// darmon.
constexpr char kKrb5ConfFile[] = "/tmp/krb5.conf";
constexpr char kCCacheFile[] = "/tmp/ccache";
// Additional kerberos canonicalization settings and default realm. kerberosd
// doesn't set a default_realm. Chrome doesn't need it as it specifies the
// principal name when invoking gssapi methods.
// TODO(acostinas, crbug.com/1087312): Set DNS canonicalization from user
// policy.
constexpr char kKrb5Settings[] =
"[libdefaults]\n"
"\tdns_canonicalize_hostname = false\n"
"\trdns = false\n"
"\tdefault_realm = %s\n";
kerberos::ErrorType GetErrorAndProto(
dbus::Response* response,
kerberos::GetKerberosFilesResponse* response_proto) {
if (!response) {
DLOG(ERROR) << "KerberosClient: Failed to call to kerberos.";
return kerberos::ERROR_DBUS_FAILURE;
}
dbus::MessageReader reader(response);
if (!reader.PopArrayOfBytesAsProto(response_proto)) {
DLOG(ERROR) << "KerberosClient: Failed to parse protobuf.";
return kerberos::ERROR_DBUS_FAILURE;
}
kerberos::ErrorType error_code = response_proto->error();
if (error_code != kerberos::ERROR_NONE) {
LOG(ERROR) << "KerberosClient: Failed to get Kerberos files with error "
<< error_code;
}
return error_code;
}
} // namespace
KerberosClient::KerberosClient(scoped_refptr<dbus::Bus> bus)
: krb5_conf_path_(kKrb5ConfFile),
krb5_ccache_path_(kCCacheFile),
kerberos_object_proxy_(bus->GetObjectProxy(
kerberos::kKerberosServiceName,
dbus::ObjectPath(kerberos::kKerberosServicePath))) {
kerberos_object_proxy_->WaitForServiceToBeAvailable(
base::BindOnce(&KerberosClient::OnKerberosServiceAvailable,
weak_ptr_factory_.GetWeakPtr()));
}
void KerberosClient::SetPrincipalName(const std::string& principal_name) {
DCHECK(kerberos_enabled_);
principal_name_ = principal_name;
if (principal_name_.empty()) {
DeleteFiles();
return;
}
GetFiles();
}
void KerberosClient::SetKerberosEnabled(bool enabled) {
kerberos_enabled_ = enabled;
if (kerberos_enabled_) {
return;
}
principal_name_ = std::string();
// Delete the krb ticket.
DeleteFiles();
}
std::string KerberosClient::krb5_ccache_path() {
return krb5_ccache_path_.MaybeAsASCII();
}
std::string KerberosClient::krb5_conf_path() {
return krb5_conf_path_.MaybeAsASCII();
}
void KerberosClient::GetFiles() {
if (principal_name_.empty() || !kerberos_enabled_) {
return;
}
LOG(INFO) << "Request kerberos files from kerberosd.";
dbus::MethodCall method_call(kerberos::kKerberosInterface,
kerberos::kGetKerberosFilesMethod);
dbus::MessageWriter writer(&method_call);
kerberos::GetKerberosFilesRequest request;
request.set_principal_name(principal_name_);
writer.AppendProtoAsArrayOfBytes(request);
kerberos_object_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&KerberosClient::OnGetFilesResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void KerberosClient::OnGetFilesResponse(dbus::Response* response) {
kerberos::GetKerberosFilesResponse response_proto;
bool success =
(GetErrorAndProto(response, &response_proto) == kerberos::ERROR_NONE);
if (success &&
(!response_proto.has_files() || !response_proto.files().has_krb5cc() ||
!response_proto.files().has_krb5conf())) {
LOG(WARNING) << "KerberosClient: Kerberos files are empty.";
success = false;
}
WriteFiles(response_proto.files().krb5cc(),
UpdateKrbConfig(response_proto.files().krb5conf()));
}
void KerberosClient::WriteFiles(const std::string& krb5_ccache_data,
const std::string& krb5_conf_data) {
bool success = !krb5_ccache_data.empty() && !krb5_conf_data.empty() &&
WriteFile(krb5_conf_path_, krb5_conf_data) &&
WriteFile(krb5_ccache_path_, krb5_ccache_data);
if (!success)
LOG(ERROR) << "Error retrieving the tickets";
}
void KerberosClient::ConnectToKerberosFilesChangedSignal() {
kerberos_object_proxy_->ConnectToSignal(
kerberos::kKerberosInterface, kerberos::kKerberosFilesChangedSignal,
base::BindRepeating(&KerberosClient::OnKerberosFilesChanged,
base::Unretained(this)),
base::BindOnce(&KerberosClient::OnKerberosFilesChangedSignalConnected,
base::Unretained(this)));
}
void KerberosClient::OnKerberosFilesChanged(dbus::Signal* signal) {
DCHECK(signal);
GetFiles();
}
void KerberosClient::OnKerberosFilesChangedSignalConnected(
const std::string& interface_name,
const std::string& signal_name,
bool success) {
DCHECK(success);
DCHECK_EQ(interface_name, kerberos::kKerberosInterface);
}
void KerberosClient::OnKerberosServiceAvailable(bool is_available) {
if (!is_available) {
LOG(ERROR) << "Kerberos service is not available";
return;
}
ConnectToKerberosFilesChangedSignal();
}
bool KerberosClient::WriteFile(const base::FilePath& path,
const std::string& blob) {
if (base::WriteFile(path, blob.c_str(), blob.size()) != blob.size()) {
LOG(ERROR) << "Failed to write file " << path.value();
return false;
}
return true;
}
void KerberosClient::DeleteFiles() {
if (base::PathExists(krb5_conf_path_)) {
if (!base::DeleteFile(krb5_conf_path_)) {
PLOG(ERROR) << "Failed to clean up the kerberos config file";
}
}
if (base::PathExists(krb5_ccache_path_)) {
if (!base::DeleteFile(krb5_ccache_path_)) {
PLOG(ERROR) << "Failed to clean up the kerberos tickets cache";
}
}
}
std::string KerberosClient::UpdateKrbConfig(const std::string& config_content) {
if (config_content.empty() || principal_name_.empty()) {
return config_content;
}
int pos = principal_name_.find("@");
if (pos == std::string::npos) {
LOG(ERROR) << "Invalid principal name";
return config_content;
}
std::string realm = principal_name_.substr(pos + 1);
std::string adjusted_config =
base::StringPrintf(kKrb5Settings, realm.c_str());
adjusted_config.append(config_content);
return adjusted_config;
}
} // namespace system_proxy