| // 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); |
| } |