blob: f71f36ba5a2e681d0f9df9790254a0216782afcb [file] [log] [blame]
Andreea Costinas922fbaf2020-05-28 11:55:22 +02001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "system-proxy/kerberos_client.h"
6
7#include <utility>
8
9#include <base/bind.h>
hscham4ce3c992021-02-19 16:37:23 +090010#include <base/callback_helpers.h>
Qijiang Fan713061e2021-03-08 15:45:12 +090011#include <base/check.h>
12#include <base/check_op.h>
Andreea Costinas922fbaf2020-05-28 11:55:22 +020013#include <base/files/file_util.h>
14#include <base/strings/stringprintf.h>
15#include <dbus/kerberos/dbus-constants.h>
16#include <dbus/message.h>
17#include <kerberos/proto_bindings/kerberos_service.pb.h>
18
19namespace system_proxy {
20
21namespace {
22// The kerberos files are written in the mount namespace of the System-proxy
23// darmon.
24constexpr char kKrb5ConfFile[] = "/tmp/krb5.conf";
25constexpr char kCCacheFile[] = "/tmp/ccache";
26
27// Additional kerberos canonicalization settings and default realm. kerberosd
28// doesn't set a default_realm. Chrome doesn't need it as it specifies the
29// principal name when invoking gssapi methods.
30// TODO(acostinas, crbug.com/1087312): Set DNS canonicalization from user
31// policy.
32constexpr char kKrb5Settings[] =
33 "[libdefaults]\n"
34 "\tdns_canonicalize_hostname = false\n"
35 "\trdns = false\n"
36 "\tdefault_realm = %s\n";
37
38kerberos::ErrorType GetErrorAndProto(
39 dbus::Response* response,
40 kerberos::GetKerberosFilesResponse* response_proto) {
41 if (!response) {
42 DLOG(ERROR) << "KerberosClient: Failed to call to kerberos.";
43 return kerberos::ERROR_DBUS_FAILURE;
44 }
45
46 dbus::MessageReader reader(response);
47 if (!reader.PopArrayOfBytesAsProto(response_proto)) {
48 DLOG(ERROR) << "KerberosClient: Failed to parse protobuf.";
49 return kerberos::ERROR_DBUS_FAILURE;
50 }
51
52 kerberos::ErrorType error_code = response_proto->error();
53 if (error_code != kerberos::ERROR_NONE) {
54 LOG(ERROR) << "KerberosClient: Failed to get Kerberos files with error "
55 << error_code;
56 }
57 return error_code;
58}
59
60} // namespace
61
62KerberosClient::KerberosClient(scoped_refptr<dbus::Bus> bus)
63 : krb5_conf_path_(kKrb5ConfFile),
64 krb5_ccache_path_(kCCacheFile),
65 kerberos_object_proxy_(bus->GetObjectProxy(
66 kerberos::kKerberosServiceName,
67 dbus::ObjectPath(kerberos::kKerberosServicePath))) {
68 kerberos_object_proxy_->WaitForServiceToBeAvailable(
69 base::BindOnce(&KerberosClient::OnKerberosServiceAvailable,
70 weak_ptr_factory_.GetWeakPtr()));
71}
72
73void KerberosClient::SetPrincipalName(const std::string& principal_name) {
74 DCHECK(kerberos_enabled_);
75 principal_name_ = principal_name;
76 if (principal_name_.empty()) {
77 DeleteFiles();
78 return;
79 }
80 GetFiles();
81}
82
83void KerberosClient::SetKerberosEnabled(bool enabled) {
84 kerberos_enabled_ = enabled;
85 if (kerberos_enabled_) {
86 return;
87 }
88 principal_name_ = std::string();
89 // Delete the krb ticket.
90 DeleteFiles();
91}
92
93std::string KerberosClient::krb5_ccache_path() {
94 return krb5_ccache_path_.MaybeAsASCII();
95}
96std::string KerberosClient::krb5_conf_path() {
97 return krb5_conf_path_.MaybeAsASCII();
98}
99
100void KerberosClient::GetFiles() {
101 if (principal_name_.empty() || !kerberos_enabled_) {
102 return;
103 }
104
105 LOG(INFO) << "Request kerberos files from kerberosd.";
106 dbus::MethodCall method_call(kerberos::kKerberosInterface,
107 kerberos::kGetKerberosFilesMethod);
108 dbus::MessageWriter writer(&method_call);
109 kerberos::GetKerberosFilesRequest request;
110 request.set_principal_name(principal_name_);
111 writer.AppendProtoAsArrayOfBytes(request);
112
113 kerberos_object_proxy_->CallMethod(
114 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
115 base::BindOnce(&KerberosClient::OnGetFilesResponse,
116 weak_ptr_factory_.GetWeakPtr()));
117}
118
119void KerberosClient::OnGetFilesResponse(dbus::Response* response) {
120 kerberos::GetKerberosFilesResponse response_proto;
121 bool success =
122 (GetErrorAndProto(response, &response_proto) == kerberos::ERROR_NONE);
123 if (success &&
124 (!response_proto.has_files() || !response_proto.files().has_krb5cc() ||
125 !response_proto.files().has_krb5conf())) {
126 LOG(WARNING) << "KerberosClient: Kerberos files are empty.";
127 success = false;
128 }
129
130 WriteFiles(response_proto.files().krb5cc(),
131 UpdateKrbConfig(response_proto.files().krb5conf()));
132}
133
134void KerberosClient::WriteFiles(const std::string& krb5_ccache_data,
135 const std::string& krb5_conf_data) {
136 bool success = !krb5_ccache_data.empty() && !krb5_conf_data.empty() &&
137 WriteFile(krb5_conf_path_, krb5_conf_data) &&
138 WriteFile(krb5_ccache_path_, krb5_ccache_data);
139 if (!success)
140 LOG(ERROR) << "Error retrieving the tickets";
141}
142
143void KerberosClient::ConnectToKerberosFilesChangedSignal() {
144 kerberos_object_proxy_->ConnectToSignal(
145 kerberos::kKerberosInterface, kerberos::kKerberosFilesChangedSignal,
146 base::BindRepeating(&KerberosClient::OnKerberosFilesChanged,
147 base::Unretained(this)),
148 base::BindOnce(&KerberosClient::OnKerberosFilesChangedSignalConnected,
149 base::Unretained(this)));
150}
151
152void KerberosClient::OnKerberosFilesChanged(dbus::Signal* signal) {
153 DCHECK(signal);
154 GetFiles();
155}
156
157void KerberosClient::OnKerberosFilesChangedSignalConnected(
158 const std::string& interface_name,
159 const std::string& signal_name,
160 bool success) {
161 DCHECK(success);
162 DCHECK_EQ(interface_name, kerberos::kKerberosInterface);
163}
164
165void KerberosClient::OnKerberosServiceAvailable(bool is_available) {
166 if (!is_available) {
167 LOG(ERROR) << "Kerberos service is not available";
168 return;
169 }
170 ConnectToKerberosFilesChangedSignal();
171}
172
173bool KerberosClient::WriteFile(const base::FilePath& path,
174 const std::string& blob) {
175 if (base::WriteFile(path, blob.c_str(), blob.size()) != blob.size()) {
176 LOG(ERROR) << "Failed to write file " << path.value();
177 return false;
178 }
179 return true;
180}
181
182void KerberosClient::DeleteFiles() {
183 if (base::PathExists(krb5_conf_path_)) {
hscham53cf73a2020-11-30 15:58:42 +0900184 if (!base::DeleteFile(krb5_conf_path_)) {
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200185 PLOG(ERROR) << "Failed to clean up the kerberos config file";
186 }
187 }
188 if (base::PathExists(krb5_ccache_path_)) {
hscham53cf73a2020-11-30 15:58:42 +0900189 if (!base::DeleteFile(krb5_ccache_path_)) {
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200190 PLOG(ERROR) << "Failed to clean up the kerberos tickets cache";
191 }
192 }
193}
194
195std::string KerberosClient::UpdateKrbConfig(const std::string& config_content) {
196 if (config_content.empty() || principal_name_.empty()) {
197 return config_content;
198 }
199
200 int pos = principal_name_.find("@");
201 if (pos == std::string::npos) {
202 LOG(ERROR) << "Invalid principal name";
203 return config_content;
204 }
205 std::string realm = principal_name_.substr(pos + 1);
206 std::string adjusted_config =
207 base::StringPrintf(kKrb5Settings, realm.c_str());
208 adjusted_config.append(config_content);
209
210 return adjusted_config;
211}
212
213} // namespace system_proxy