blob: cef5fc113541b4eae52194741007021cf94363c2 [file] [log] [blame]
Lutz Justen09cd1c32019-02-15 14:31:49 +01001// Copyright 2019 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
Lutz Justene39cbd42019-05-14 14:52:24 +02005#include "kerberos/krb5_interface_impl.h"
Lutz Justen09cd1c32019-02-15 14:31:49 +01006
Lutz Justencb8399d2019-03-08 14:30:17 +01007#include <algorithm>
Lutz Justen09cd1c32019-02-15 14:31:49 +01008#include <utility>
9
Lutz Justenb79da832019-03-08 14:52:53 +010010#include <base/files/file_path.h>
Lutz Justen90281402019-07-05 15:14:37 +020011#include <base/files/file_util.h>
Lutz Justen09cd1c32019-02-15 14:31:49 +010012#include <base/logging.h>
13#include <base/strings/stringprintf.h>
14#include <krb5.h>
15
Lutz Justen90281402019-07-05 15:14:37 +020016#include "kerberos/error_strings.h"
17
Lutz Justen09cd1c32019-02-15 14:31:49 +010018namespace kerberos {
19
20namespace {
21
22// Environment variable for the Kerberos configuration (krb5.conf).
23constexpr char kKrb5ConfigEnvVar[] = "KRB5_CONFIG";
24
Lutz Justencb8399d2019-03-08 14:30:17 +010025// Wrapper classes for safe construction and destruction.
26struct ScopedKrb5Context {
27 ScopedKrb5Context() = default;
28 ~ScopedKrb5Context() {
29 if (ctx) {
30 krb5_free_context(ctx);
31 ctx = nullptr;
32 }
33 }
34
35 // Converts the krb5 |code| to a human readable error message.
36 std::string GetErrorMessage(errcode_t code) {
Lutz Justene5238762019-06-06 14:09:21 +020037 // Fallback if error happens during ctx initialization (e.g. bad config).
38 if (!ctx)
39 return base::StringPrintf("Error %ld", code);
40
Lutz Justencb8399d2019-03-08 14:30:17 +010041 const char* emsg = krb5_get_error_message(ctx, code);
42 std::string msg = base::StringPrintf("%s (%ld)", emsg, code);
43 krb5_free_error_message(ctx, emsg);
44 return msg;
45 }
46
47 krb5_context get() const { return ctx; }
48 krb5_context* get_mutable_ptr() { return &ctx; }
49
50 private:
51 krb5_context ctx = nullptr;
52};
53
54struct ScopedKrb5CCache {
Lutz Justen1cb62412019-05-09 10:09:45 +020055 // Prefer the constructor taking a context if possible.
56 ScopedKrb5CCache() {}
57 explicit ScopedKrb5CCache(krb5_context _ctx) { set_ctx(_ctx); }
58
Lutz Justencb8399d2019-03-08 14:30:17 +010059 ~ScopedKrb5CCache() {
60 if (ccache) {
Lutz Justen1cb62412019-05-09 10:09:45 +020061 DCHECK(ctx);
62 krb5_cc_close(ctx, ccache);
Lutz Justencb8399d2019-03-08 14:30:17 +010063 ccache = nullptr;
64 }
65 }
66
Lutz Justen1cb62412019-05-09 10:09:45 +020067 // The context must be set if |ccache| is set (though get_mutable_ptr())
68 // before this object is destroyed.
69 void set_ctx(krb5_context _ctx) {
70 ctx = _ctx;
71 DCHECK(ctx);
72 }
73
Lutz Justencb8399d2019-03-08 14:30:17 +010074 krb5_ccache get() const { return ccache; }
75 krb5_ccache* get_mutable_ptr() { return &ccache; }
76
77 private:
78 // Pointer to parent data, not owned.
Lutz Justen1cb62412019-05-09 10:09:45 +020079 krb5_context ctx = nullptr;
Lutz Justencb8399d2019-03-08 14:30:17 +010080 krb5_ccache ccache = nullptr;
81};
82
83// Maps some common krb5 error codes to our internal codes. If something is not
84// reported properly, add more cases here.
85ErrorType TranslateErrorCode(errcode_t code) {
86 switch (code) {
87 case KRB5KDC_ERR_NONE:
88 return ERROR_NONE;
89
90 case KRB5_KDC_UNREACH:
91 return ERROR_NETWORK_PROBLEM;
92
Lutz Justene5238762019-06-06 14:09:21 +020093 case KRB5_CONFIG_BADFORMAT:
94 return ERROR_BAD_CONFIG;
95
Lutz Justencb8399d2019-03-08 14:30:17 +010096 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
97 return ERROR_BAD_PRINCIPAL;
98
99 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
100 case KRB5KDC_ERR_PREAUTH_FAILED:
101 return ERROR_BAD_PASSWORD;
102
103 case KRB5KDC_ERR_KEY_EXP:
104 return ERROR_PASSWORD_EXPIRED;
105
106 // TODO(https://crbug.com/951741): Verify
107 case KRB5_KPASSWD_SOFTERROR:
108 return ERROR_PASSWORD_REJECTED;
109
110 // TODO(https://crbug.com/951741): Verify
111 case KRB5_FCC_NOFILE:
112 return ERROR_NO_CREDENTIALS_CACHE_FOUND;
113
114 // TODO(https://crbug.com/951741): Verify
115 case KRB5KRB_AP_ERR_TKT_EXPIRED:
116 return ERROR_KERBEROS_TICKET_EXPIRED;
117
118 case KRB5KDC_ERR_ETYPE_NOSUPP:
119 return ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE;
120
121 case KRB5_REALM_UNKNOWN:
122 return ERROR_CONTACTING_KDC_FAILED;
123
124 default:
125 return ERROR_UNKNOWN_KRB5_ERROR;
126 }
127}
128
129// Returns true if the string contained in |data| matches |str_to_match|.
130bool DataMatches(const krb5_data& data, const char* str_to_match) {
131 // It is not clear whether data.data is null terminated, so a strcmp might
132 // not work.
133 return strlen(str_to_match) == data.length &&
134 memcmp(str_to_match, data.data, data.length) == 0;
135}
136
137// Returns true if |creds| has a server that starts with "krbtgt".
138bool IsTgt(const krb5_creds& creds) {
139 return creds.server && creds.server->length > 0 &&
140 DataMatches(creds.server->data[0], "krbtgt");
141}
142
Lutz Justen09cd1c32019-02-15 14:31:49 +0100143enum class Action { AcquireTgt, RenewTgt };
144
145struct Options {
146 std::string principal_name;
147 std::string password;
148 std::string krb5cc_path;
149 std::string config_path;
150 Action action = Action::AcquireTgt;
151};
152
153// Encapsulates krb5 context data required for kinit.
154class KinitContext {
155 public:
156 explicit KinitContext(Options options) : options_(std::move(options)) {
157 memset(&k5_, 0, sizeof(k5_));
158 }
159
160 // Runs kinit with the options passed to the constructor. Only call once per
161 // context. While in principle it should be fine to run multiple times, the
162 // code path probably hasn't been tested (kinit does not call this multiple
163 // times).
164 ErrorType Run() {
165 DCHECK(!did_run_);
166 did_run_ = true;
167
168 ErrorType error = Initialize();
169 if (error == ERROR_NONE)
170 error = RunKinit();
171 Finalize();
172 return error;
173 }
174
175 private:
176 // The following code has been adapted from kinit.c in the mit-krb5 code base.
177 // It has been formatted to fit this screen.
178
179 struct Krb5Data {
Lutz Justen09cd1c32019-02-15 14:31:49 +0100180 krb5_principal me;
181 char* name;
182 };
183
184 // Wrapper around krb5 data to get rid of the gotos in the original code.
185 struct KInitData {
186 // Pointer to parent data, not owned.
Lutz Justencb8399d2019-03-08 14:30:17 +0100187 const krb5_context ctx = nullptr;
188 // Pointer to parent data, not owned.
Lutz Justen09cd1c32019-02-15 14:31:49 +0100189 const Krb5Data* k5 = nullptr;
190 krb5_creds my_creds;
191 krb5_get_init_creds_opt* options = nullptr;
192
193 // The lifetime of the |k5| pointer must exceed the lifetime of this object.
Lutz Justencb8399d2019-03-08 14:30:17 +0100194 explicit KInitData(const krb5_context ctx, const Krb5Data* k5)
195 : ctx(ctx), k5(k5) {
Lutz Justen09cd1c32019-02-15 14:31:49 +0100196 memset(&my_creds, 0, sizeof(my_creds));
197 }
198
199 ~KInitData() {
200 if (options)
Lutz Justencb8399d2019-03-08 14:30:17 +0100201 krb5_get_init_creds_opt_free(ctx, options);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100202 if (my_creds.client == k5->me)
203 my_creds.client = nullptr;
Lutz Justencb8399d2019-03-08 14:30:17 +0100204 krb5_free_cred_contents(ctx, &my_creds);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100205 }
206 };
207
Lutz Justen09cd1c32019-02-15 14:31:49 +0100208 // Initializes krb5 data.
209 ErrorType Initialize() {
Lutz Justencb8399d2019-03-08 14:30:17 +0100210 krb5_error_code ret = krb5_init_context(ctx.get_mutable_ptr());
Lutz Justen09cd1c32019-02-15 14:31:49 +0100211 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100212 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while initializing context";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100213 return TranslateErrorCode(ret);
214 }
215
Lutz Justen1cb62412019-05-09 10:09:45 +0200216 out_cc.set_ctx(ctx.get());
217 ret = krb5_cc_resolve(ctx.get(), options_.krb5cc_path.c_str(),
218 out_cc.get_mutable_ptr());
Lutz Justen09cd1c32019-02-15 14:31:49 +0100219 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100220 LOG(ERROR) << ctx.GetErrorMessage(ret) << " resolving ccache";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100221 return TranslateErrorCode(ret);
222 }
223
Lutz Justencb8399d2019-03-08 14:30:17 +0100224 ret = krb5_parse_name_flags(ctx.get(), options_.principal_name.c_str(),
Lutz Justen09cd1c32019-02-15 14:31:49 +0100225 0 /* flags */, &k5_.me);
226 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100227 LOG(ERROR) << ctx.GetErrorMessage(ret) << " when parsing name";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100228 return TranslateErrorCode(ret);
229 }
230
Lutz Justencb8399d2019-03-08 14:30:17 +0100231 ret = krb5_unparse_name(ctx.get(), k5_.me, &k5_.name);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100232 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100233 LOG(ERROR) << ctx.GetErrorMessage(ret) << " when unparsing name";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100234 return TranslateErrorCode(ret);
235 }
236
237 options_.principal_name = k5_.name;
238 return ERROR_NONE;
239 }
240
241 // Finalizes krb5 data.
242 void Finalize() {
Lutz Justencb8399d2019-03-08 14:30:17 +0100243 krb5_free_unparsed_name(ctx.get(), k5_.name);
244 krb5_free_principal(ctx.get(), k5_.me);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100245 memset(&k5_, 0, sizeof(k5_));
246 }
247
248 // Runs the actual kinit code and acquires/renews tickets.
249 ErrorType RunKinit() {
250 krb5_error_code ret;
Lutz Justencb8399d2019-03-08 14:30:17 +0100251 KInitData d(ctx.get(), &k5_);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100252
Lutz Justencb8399d2019-03-08 14:30:17 +0100253 ret = krb5_get_init_creds_opt_alloc(ctx.get(), &d.options);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100254 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100255 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while getting options";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100256 return TranslateErrorCode(ret);
257 }
258
Lutz Justencb8399d2019-03-08 14:30:17 +0100259 ret = krb5_get_init_creds_opt_set_out_ccache(ctx.get(), d.options,
Lutz Justen1cb62412019-05-09 10:09:45 +0200260 out_cc.get());
Lutz Justen09cd1c32019-02-15 14:31:49 +0100261 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100262 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while getting options";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100263 return TranslateErrorCode(ret);
264 }
265
266 // To get notified of expiry, see
267 // krb5_get_init_creds_opt_set_expire_callback
268
269 switch (options_.action) {
270 case Action::AcquireTgt:
271 ret = krb5_get_init_creds_password(
Lutz Justencb8399d2019-03-08 14:30:17 +0100272 ctx.get(), &d.my_creds, k5_.me, options_.password.c_str(),
Lutz Justen09cd1c32019-02-15 14:31:49 +0100273 nullptr /* prompter */, nullptr /* data */, 0 /* start_time */,
274 nullptr /* in_tkt_service */, d.options);
275 break;
276 case Action::RenewTgt:
Lutz Justen1cb62412019-05-09 10:09:45 +0200277 ret =
278 krb5_get_renewed_creds(ctx.get(), &d.my_creds, k5_.me, out_cc.get(),
279 nullptr /* options_.in_tkt_service */);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100280 break;
281 }
282
283 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100284 LOG(ERROR) << ctx.GetErrorMessage(ret);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100285 return TranslateErrorCode(ret);
286 }
287
288 if (options_.action != Action::AcquireTgt) {
Lutz Justen1cb62412019-05-09 10:09:45 +0200289 ret = krb5_cc_initialize(ctx.get(), out_cc.get(), k5_.me);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100290 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100291 LOG(ERROR) << ctx.GetErrorMessage(ret) << " when initializing cache";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100292 return TranslateErrorCode(ret);
293 }
294
Lutz Justen1cb62412019-05-09 10:09:45 +0200295 ret = krb5_cc_store_cred(ctx.get(), out_cc.get(), &d.my_creds);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100296 if (ret) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100297 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while storing credentials";
Lutz Justen09cd1c32019-02-15 14:31:49 +0100298 return TranslateErrorCode(ret);
299 }
300 }
301
302 return ERROR_NONE;
303 }
304
Lutz Justencb8399d2019-03-08 14:30:17 +0100305 ScopedKrb5Context ctx;
Lutz Justen1cb62412019-05-09 10:09:45 +0200306 ScopedKrb5CCache out_cc;
Lutz Justen09cd1c32019-02-15 14:31:49 +0100307 Krb5Data k5_;
308 Options options_;
309 bool did_run_ = false;
310};
311
Lutz Justen90281402019-07-05 15:14:37 +0200312// Runs the Kerberos configuration |krb5conf| through the krb5 code to see if it
313// can be parsed.
314ErrorType ValidateConfigViaKrb5(const std::string& krb5conf) {
315 // Since krb5 doesn't accept config passed as string, write it to disk.
316 base::FilePath krb5conf_path;
317 if (!base::CreateTemporaryFile(&krb5conf_path)) {
318 LOG(ERROR) << "Failed to create temp file for validating config";
319 return ERROR_LOCAL_IO;
320 }
321
322 const int size = static_cast<int>(krb5conf.size());
323 if (base::WriteFile(krb5conf_path, krb5conf.data(), size) != size) {
324 LOG(ERROR) << "Failed to write config to disk at " << krb5conf_path.value()
325 << " for validating config";
326 return ERROR_LOCAL_IO;
327 }
328
329 // krb5_init_context parses the config file.
330 setenv(kKrb5ConfigEnvVar, krb5conf_path.value().c_str(), 1);
331 ScopedKrb5Context ctx;
332 krb5_error_code ret = krb5_init_context(ctx.get_mutable_ptr());
333 unsetenv(kKrb5ConfigEnvVar);
334 base::DeleteFile(krb5conf_path, false /* recursive */);
335
336 if (ret) {
337 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while initializing context";
338 return TranslateErrorCode(ret);
339 }
340
341 return ERROR_NONE;
342}
343
Lutz Justen09cd1c32019-02-15 14:31:49 +0100344} // namespace
345
Lutz Justene39cbd42019-05-14 14:52:24 +0200346Krb5InterfaceImpl::Krb5InterfaceImpl() = default;
Lutz Justen09cd1c32019-02-15 14:31:49 +0100347
Lutz Justene39cbd42019-05-14 14:52:24 +0200348Krb5InterfaceImpl::~Krb5InterfaceImpl() = default;
Lutz Justen09cd1c32019-02-15 14:31:49 +0100349
Lutz Justene39cbd42019-05-14 14:52:24 +0200350ErrorType Krb5InterfaceImpl::AcquireTgt(const std::string& principal_name,
351 const std::string& password,
352 const base::FilePath& krb5cc_path,
353 const base::FilePath& krb5conf_path) {
Lutz Justen09cd1c32019-02-15 14:31:49 +0100354 Options options;
355 options.action = Action::AcquireTgt;
356 options.principal_name = principal_name;
357 options.password = password;
Lutz Justenb79da832019-03-08 14:52:53 +0100358 options.krb5cc_path = krb5cc_path.value();
359 setenv(kKrb5ConfigEnvVar, krb5conf_path.value().c_str(), 1);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100360 ErrorType error = KinitContext(std::move(options)).Run();
361 unsetenv(kKrb5ConfigEnvVar);
362 return error;
363}
364
Lutz Justene39cbd42019-05-14 14:52:24 +0200365ErrorType Krb5InterfaceImpl::RenewTgt(const std::string& principal_name,
366 const base::FilePath& krb5cc_path,
Lutz Justene6784c02019-07-03 14:08:43 +0200367 const base::FilePath& krb5conf_path) {
Lutz Justen09cd1c32019-02-15 14:31:49 +0100368 Options options;
369 options.action = Action::RenewTgt;
370 options.principal_name = principal_name;
Lutz Justenb79da832019-03-08 14:52:53 +0100371 options.krb5cc_path = krb5cc_path.value();
Lutz Justene6784c02019-07-03 14:08:43 +0200372 setenv(kKrb5ConfigEnvVar, krb5conf_path.value().c_str(), 1);
Lutz Justen09cd1c32019-02-15 14:31:49 +0100373 ErrorType error = KinitContext(std::move(options)).Run();
374 unsetenv(kKrb5ConfigEnvVar);
375 return error;
376}
377
Lutz Justene39cbd42019-05-14 14:52:24 +0200378ErrorType Krb5InterfaceImpl::GetTgtStatus(const base::FilePath& krb5cc_path,
379 TgtStatus* status) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100380 DCHECK(status);
381
382 ScopedKrb5Context ctx;
383 krb5_error_code ret = krb5_init_context(ctx.get_mutable_ptr());
384 if (ret) {
385 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while initializing context";
386 return TranslateErrorCode(ret);
387 }
388
Lutz Justen1cb62412019-05-09 10:09:45 +0200389 ScopedKrb5CCache ccache(ctx.get());
Lutz Justencb8399d2019-03-08 14:30:17 +0100390 std::string prefixed_krb5cc_path = "FILE:" + krb5cc_path.value();
391 ret = krb5_cc_resolve(ctx.get(), prefixed_krb5cc_path.c_str(),
Lutz Justen1cb62412019-05-09 10:09:45 +0200392 ccache.get_mutable_ptr());
Lutz Justencb8399d2019-03-08 14:30:17 +0100393 if (ret) {
Lutz Justen1cb62412019-05-09 10:09:45 +0200394 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while resolving cache";
Lutz Justencb8399d2019-03-08 14:30:17 +0100395 return TranslateErrorCode(ret);
396 }
397
398 krb5_cc_cursor cur;
Lutz Justen1cb62412019-05-09 10:09:45 +0200399 ret = krb5_cc_start_seq_get(ctx.get(), ccache.get(), &cur);
Lutz Justencb8399d2019-03-08 14:30:17 +0100400 if (ret) {
401 LOG(ERROR) << ctx.GetErrorMessage(ret)
402 << " while starting to retrieve tickets";
403 return TranslateErrorCode(ret);
404 }
405
406 krb5_timestamp now = time(nullptr);
407
408 krb5_creds creds;
409 bool found_tgt = false;
Lutz Justen1cb62412019-05-09 10:09:45 +0200410 while ((ret = krb5_cc_next_cred(ctx.get(), ccache.get(), &cur, &creds)) ==
411 0) {
Lutz Justencb8399d2019-03-08 14:30:17 +0100412 if (IsTgt(creds)) {
413 if (creds.times.endtime)
414 status->validity_seconds =
415 std::max<int64_t>(creds.times.endtime - now, 0);
416
417 if (creds.times.renew_till) {
418 status->renewal_seconds =
419 std::max<int64_t>(creds.times.renew_till - now, 0);
420 }
421
422 if (found_tgt) {
423 LOG(WARNING) << "More than one TGT found in credential cache '"
424 << krb5cc_path.value() << ".";
425 }
426 found_tgt = true;
427 }
428 krb5_free_cred_contents(ctx.get(), &creds);
429 }
430 if (!found_tgt) {
431 LOG(WARNING) << "No TGT found in credential cache '" << krb5cc_path.value()
432 << ".";
433 }
434
435 if (ret != KRB5_CC_END) {
436 LOG(ERROR) << ctx.GetErrorMessage(ret) << " while retrieving a ticket";
437 return TranslateErrorCode(ret);
438 }
439
Lutz Justen1cb62412019-05-09 10:09:45 +0200440 ret = krb5_cc_end_seq_get(ctx.get(), ccache.get(), &cur);
Lutz Justencb8399d2019-03-08 14:30:17 +0100441 if (ret) {
442 LOG(ERROR) << ctx.GetErrorMessage(ret)
443 << " while finishing ticket retrieval";
444 return TranslateErrorCode(ret);
445 }
446
447 return ERROR_NONE;
448}
449
Lutz Justen90281402019-07-05 15:14:37 +0200450ErrorType Krb5InterfaceImpl::ValidateConfig(const std::string& krb5conf,
451 ConfigErrorInfo* error_info) {
452 if (!config_validator_disabled_for_testing) {
453 *error_info = config_validator_.Validate(krb5conf);
454 if (error_info->code() != CONFIG_ERROR_NONE)
455 return ERROR_BAD_CONFIG;
456 }
457
458 // Also try the mit krb5 code to parse the config.
459 error_info->Clear();
460 ErrorType error = ValidateConfigViaKrb5(krb5conf);
461 if (error == ERROR_BAD_CONFIG) {
462 error_info->set_code(CONFIG_ERROR_KRB5_FAILED_TO_PARSE);
463 return error;
464 }
465
466 // Ignore all other errors, they're most likely unrelated. The
467 // |config_validator_| should already cover pretty much everything, anyway.
468 error_info->set_code(CONFIG_ERROR_NONE);
469 if (error != ERROR_NONE) {
470 LOG(WARNING) << "Ignoring unrelated error " << GetErrorString(error)
471 << " while validating config";
472 }
473 return error;
474}
475
Lutz Justen09cd1c32019-02-15 14:31:49 +0100476} // namespace kerberos