blob: 294e376ef2a2ab42c6eae9f30bfca517dd3e37e6 [file] [log] [blame]
// Copyright 2017 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.
// Stub implementation of kinit. Does not talk to server, but simply returns
// fixed responses to predefined input.
#include <string>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "authpolicy/platform_helper.h"
#include "authpolicy/samba_helper.h"
#include "authpolicy/stub_common.h"
namespace authpolicy {
namespace {
// kinit error messages. stub_kinit reproduces kinit errors because authpolicy
// reads and interprets error messages from stdout/stderr.
const char kNonExistingPrincipalErrorFormat[] =
"kinit: Client '%s' not found in Kerberos database while getting initial "
"credentials";
const char kWrongPasswordError[] =
"kinit: Preauthentication failed while getting initial credentials";
const char kPasswordExpiredStdout[] =
"Password expired. You must change it now.";
const char kPasswordRejectedStdout[] =
"Password for user@realm:"
"Password expired. You must change it now.\n"
"Enter new password:\n"
"Enter it again:\n"
"Password change rejected: The password must include numbers or symbols. "
"Don't include any part of your name in the password. The password must "
"contain at least 7 characters. The password must be different from the "
"previous 24 passwords. The password can only be changed once a day.. "
"Please try again.";
const char kCannotReadPasswordStderr[] =
"Cannot read password while getting initial credentials";
const char kNetworkError[] = "Cannot resolve network address for KDC in realm";
const char kCannotContactKdc[] = "Cannot contact any KDC";
const char kKdcIpKey[] = "kdc = [";
const char kPasswordWillExpireWarning[] =
"Warning: Your password will expire in 7 days on Fri May 19 14:28:41 2017";
const char kRefresh[] = "-R";
const char kTicketExpired[] =
"kinit: Ticket expired while renewing credentials";
const char kEncTypeNotSupported[] =
"KDC has no support for encryption type while getting initial credentials";
// Returns upper-cased |machine_name|$@|kUserRealm|.
std::string MakeMachinePrincipal(const std::string& machine_name) {
return base::ToUpperASCII(machine_name) + "$@" + kUserRealm;
}
// For a given |machine_name|, tests if the |command_line| starts with
// corresponding machine principal part (upper-cased |machine_name| + "$@").
bool TestMachinePrincipal(const std::string& command_line,
const std::string& machine_name) {
std::string machine_principal_part = base::ToUpperASCII(machine_name) + "$@";
return StartsWithCaseSensitive(command_line, machine_principal_part.c_str());
}
// Returns true if |command_line| contains a machine principal and not a user
// principal.
bool HasMachinePrincipal(const std::string& command_line) {
return Contains(command_line, "$@");
}
// Returns false for the first |kNumPropagationRetries| times the method is
// called and true afterwards. Used to simulate account propagation errors. Only
// works once per test. Uses a test file internally, where each time a byte is
// appended to count retries. Note that each invokation usually happens in a
// separate process, so a static memory location can't be used for counting.
bool HasStubAccountPropagated() {
const auto test_dir = base::FilePath(GetKrb5ConfFilePath()).DirName();
return PostIncTestCounter(test_dir) == kNumPropagationRetries;
}
// Reads the contents of the file at |kExpectedMachinePassFilename| and returns
// it in |expected_machine_pass|. Returns false if it doesn't exist.
bool GetExpectedMachinePassword(std::string* expected_machine_pass) {
const base::FilePath krb5_conf_path(GetKrb5ConfFilePath());
const base::FilePath expected_password_path =
krb5_conf_path.DirName().Append(kExpectedMachinePassFilename);
if (!base::PathExists(expected_password_path))
return false;
CHECK(base::ReadFileToString(base::FilePath(expected_password_path),
expected_machine_pass));
return true;
}
// Writes a stub Kerberos credentials cache to the file path given by the
// kKrb5CCEnvKey environment variable.
void WriteKrb5CC(const std::string& data) {
const std::string krb5cc_path = GetKrb5CCFilePath();
// Note: base::WriteFile triggers a seccomp failure, so do it old-school.
base::ScopedFILE krb5cc_file(fopen(krb5cc_path.c_str(), "w"));
CHECK(krb5cc_file);
CHECK_EQ(1U, fwrite(data.c_str(), data.size(), 1, krb5cc_file.get()));
}
// Checks whether the Kerberos configuration file contains the KDC IP.
bool Krb5ConfContainsKdcIp() {
const base::FilePath krb5_conf_path(GetKrb5ConfFilePath());
std::string krb5_conf;
CHECK(base::ReadFileToString(krb5_conf_path, &krb5_conf));
return Contains(krb5_conf, kKdcIpKey);
}
// Handles ticket refresh with kinit -R. Switches behavior based on the contents
// of the Kerberos ticket.
int HandleRefresh() {
const std::string krb5cc_path = GetKrb5CCFilePath();
std::string krb5cc_data;
CHECK(base::ReadFileToString(base::FilePath(krb5cc_path), &krb5cc_data));
if (krb5cc_data == kExpiredKrb5CCData) {
WriteOutput("", kTicketExpired);
return kExitCodeError;
}
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
int HandleCommandLine(const std::string& command_line) {
// Read the password from stdin.
std::string password;
if (!ReadPipeToString(STDIN_FILENO, &password)) {
LOG(ERROR) << "Failed to read password";
return kExitCodeError;
}
// Request for TGT refresh. The only test that uses it expects a failure.
if (StartsWithCaseSensitive(command_line, kRefresh))
return HandleRefresh();
// Stub non-existing account error.
if (StartsWithCaseSensitive(command_line, kNonExistingUserPrincipal)) {
WriteOutput("", base::StringPrintf(kNonExistingPrincipalErrorFormat,
kNonExistingUserPrincipal));
return kExitCodeError;
}
// Stub network error.
if (StartsWithCaseSensitive(command_line, kNetworkErrorUserPrincipal)) {
WriteOutput("", kNetworkError);
return kExitCodeError;
}
// Stub kinit retry if the krb5.conf contains the KDC IP.
if (StartsWithCaseSensitive(command_line, kKdcRetryUserPrincipal)) {
if (Krb5ConfContainsKdcIp()) {
WriteOutput("", kCannotContactKdc);
return kExitCodeError;
}
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
// Stub kinit retry, but fail the second time as well.
if (StartsWithCaseSensitive(command_line, kKdcRetryFailsUserPrincipal)) {
WriteOutput("", kCannotContactKdc);
return kExitCodeError;
}
// Stub encryption type not supported error.
if (StartsWithCaseSensitive(command_line,
kEncTypeNotSupportedUserPrincipal)) {
WriteOutput("", kEncTypeNotSupported);
return kExitCodeError;
}
// Stub expired credential cache.
if (StartsWithCaseSensitive(command_line, kExpiredTgtUserPrincipal)) {
WriteKrb5CC(kExpiredKrb5CCData);
return kExitCodeOk;
}
// Stub seccomp failure.
if (StartsWithCaseSensitive(command_line, kSeccompUserPrincipal)) {
TriggerSeccompFailure();
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
// Stub valid user principal. Switch behavior based on password.
if (StartsWithCaseSensitive(command_line, kUserPrincipal) ||
StartsWithCaseSensitive(command_line, kPasswordChangedUserPrincipal) ||
StartsWithCaseSensitive(command_line, kNoPwdFieldsUserPrincipal)) {
// Stub wrong password error.
if (password == kWrongPassword) {
WriteOutput("", kWrongPasswordError);
return kExitCodeError;
}
// Stub expired password error.
if (password == kExpiredPassword) {
WriteOutput(kPasswordExpiredStdout, kCannotReadPasswordStderr);
return kExitCodeError;
}
// Stub rejected password error.
if (password == kRejectedPassword) {
WriteOutput(kPasswordRejectedStdout, kCannotReadPasswordStderr);
return kExitCodeError;
}
// Stub warning that the password will expire soon.
if (password == kWillExpirePassword) {
WriteKrb5CC(kValidKrb5CCData);
WriteOutput(kPasswordWillExpireWarning, "");
return kExitCodeOk;
}
// Stub valid password.
if (password == kPassword) {
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
NOTREACHED() << "UNHANDLED PASSWORD " << password;
return kExitCodeError;
}
// Handle machine principals.
if (HasMachinePrincipal(command_line)) {
// Stub account propagation error.
if (TestMachinePrincipal(command_line, kExpectKeytabMachineName)) {
// Make sure the caller adds the debug level.
CHECK(Contains(command_line, kUseKeytabParam));
CHECK(password.empty());
std::string keytab_path = GetKeytabFilePath();
CHECK(!keytab_path.empty());
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
// The ones below should be using a password.
CheckMachinePassword(password);
// Compare to the expected password, if it exists.
std::string expected_password;
if (GetExpectedMachinePassword(&expected_password) &&
password != expected_password) {
WriteOutput("", kWrongPasswordError);
return kExitCodeError;
}
// Stub account propagation error.
if (TestMachinePrincipal(command_line, kPropagationRetryMachineName) &&
!HasStubAccountPropagated()) {
WriteOutput(
"", base::StringPrintf(
kNonExistingPrincipalErrorFormat,
MakeMachinePrincipal(kPropagationRetryMachineName).c_str()));
return kExitCodeError;
}
// Stub non-existent machine error (e.g. machine got deleted from ACtive
// Directory).
if (TestMachinePrincipal(command_line, kNonExistingMachineName)) {
// Note: Same error as if the account hasn't propagated yet.
WriteOutput("",
base::StringPrintf(
kNonExistingPrincipalErrorFormat,
MakeMachinePrincipal(kNonExistingMachineName).c_str()));
return kExitCodeError;
}
// All other machine principals just pass.
WriteKrb5CC(kValidKrb5CCData);
return kExitCodeOk;
}
NOTREACHED() << "UNHANDLED COMMAND LINE " << command_line;
return kExitCodeError;
}
} // namespace
} // namespace authpolicy
int main(int argc, char* argv[]) {
std::string command_line = authpolicy::GetCommandLine(argc, argv);
return authpolicy::HandleCommandLine(command_line);
}