| // 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_, false /*recursive*/)) { |
| PLOG(ERROR) << "Failed to clean up the kerberos config file"; |
| } |
| } |
| if (base::PathExists(krb5_ccache_path_)) { |
| if (!base::DeleteFile(krb5_ccache_path_, false /*recursive*/)) { |
| 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 |