blob: b7e1352069bdce0594e0f5e3b35e4b59999f9787 [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 Samba net. 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/constants.h"
#include "authpolicy/platform_helper.h"
#include "authpolicy/samba_helper.h"
#include "authpolicy/stub_common.h"
namespace authpolicy {
namespace {
const char kStubKeytab[] = "Stub keytab file";
// Various stub error messages.
const char kSmbConfArgMissingError[] =
"Can't load /etc/samba/smb.conf - run testparm to debug it";
const char kNetworkError[] = "No logon servers";
const char kWrongPasswordError[] =
"Failed to join domain: failed to lookup DC info for domain 'REALM.COM' "
"over rpc: Logon failure";
const char kExpiredPasswordError[] =
"Enter user@REALM.COM's password:\n"
"Failed to join domain: failed to lookup DC info for domain 'REALM.COM' "
"over rpc: Must change password";
const char kJoinAccessDeniedError[] =
"Failed to join domain: Failed to set account flags for machine account "
"(NT_STATUS_ACCESS_DENIED)";
const char kMachineNameTooLongError[] =
"Our netbios name can be at most %zd chars long, \"%s\" is %zd chars long\n"
"Failed to join domain: The format of the specified computer name is "
"invalid.";
const char kInvalidMachineNameError[] =
"Failed to join domain: failed to join domain 'REALM.COM' over rpc: "
"Improperly formed account name";
const char kInsufficientQuotaError[] =
"Insufficient quota exists to complete the operation";
// Size limit for machine name.
const size_t kMaxMachineNameSize = 15;
// Stub net ads info response.
const char kStubInfo[] = R"!!!(LDAP server: 111.222.33.44
LDAP server name: dcname.realm.com
Realm: REALM.COM
Bind Path: dc=REALM,dc=COM
LDAP port: 389
Server time: Fri, 03 Feb 2017 05:24:05 PST
KDC server: 111.222.33.44
Server time offset: -91
Last machine account password change:
Wed, 31 Dec 1969 16:00:00 PST)!!!";
// Stub net ads gpo list response.
const char kStubLocalGpo[] = R"!!!(---------------------
name: Local Policy
displayname: Local Policy
version: 0 (0x00000000)
version_user: 0 (0x0000)
version_machine: 0 (0x0000)
filesyspath: (null)
dspath: (null)
options: 0 GPFLAGS_ALL_ENABLED
link: (null)
link_type: 5 machine_extensions: (null)
user_extensions: (null)
)!!!";
const char kStubRemoteGpo[] = R"!!!(---------------------
name: %s
displayname: test-user-policy
version: %u (0x%04x%04x)
version_user: %u (0x%04x)
version_machine: %u (0x%04x)
filesyspath: \\realm.com\SysVol\realm.com\Policies\%s
dspath: cn=%s,cn=policies,cn=system,DC=chrome,DC=lan
options: %s
link: OU=test-ou,DC=chrome,DC=lan
link_type: 4 GP_LINK_OU
machine_extensions: (null)
user_extensions: [{D02B1F73-3407-48AE-BA88-E8213C6761F1}]
)!!!";
// Stub net ads search response.
const char kStubSearchFormat[] = R"!!!(Got 1 replies
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: %s
sn: Doe
givenName: %s
initials: JD
distinguishedName: CN=%s,OU=some-ou,DC=realm,DC=com
instanceType: 4
whenCreated: 20161018155136.0Z
whenChanged: 20170217134227.0Z
displayName: %s
uSNCreated: 287406
uSNChanged: 307152
name: John Doe
objectGUID: %s
userAccountControl: %u
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 131309487458845506
lastLogoff: 0
lastLogon: 131320568639495686
pwdLastSet: %lu
primaryGroupID: 513
objectSid: S-1-5-21-250062649-3667841115-373469193-1134
accountExpires: 9223372036854775807
logonCount: 1453
sAMAccountName: %s
sAMAccountType: 805306368
userPrincipalName: jdoe@realm.com
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=chrome,DC=lan
dSCorePropagationData: 20161024075536.0Z
dSCorePropagationData: 20161024075311.0Z
dSCorePropagationData: 20161019075502.0Z
dSCorePropagationData: 16010101000000.0Z
lastLogonTimestamp: 131318125471489990
msDS-SupportedEncryptionTypes: 0)!!!";
// Search that doesn't find anything.
const char kStubBadSearch[] = "Got 0 replies";
// Builder for custom search results (without having a 7-line base::StringPrintf
// every time). Usage:
// search_result = SearchBuilder().SetDisplayName("John Doe").GetResult();
class SearchBuilder {
public:
// Prints out a stub net ads search result with the set parameters.
std::string GetResult() {
return base::StringPrintf(
kStubSearchFormat, common_name_.c_str(), given_name_.c_str(),
common_name_.c_str(), display_name_.c_str(), object_guid_.c_str(),
user_account_control_, pwd_last_set_, sam_account_name_.c_str());
}
// Sets the value of the givenName key.
SearchBuilder& SetGivenName(const std::string& value) {
given_name_ = value;
return *this;
}
// Sets the value of the displayName key.
SearchBuilder& SetDisplayName(const std::string& value) {
display_name_ = value;
return *this;
}
// Sets the value of the objectUID key.
SearchBuilder& SetObjectGuid(const std::string& value) {
object_guid_ = value;
return *this;
}
// Sets the value of the sAMAccountName key.
SearchBuilder& SetSAMAccountName(const std::string& value) {
sam_account_name_ = value;
return *this;
}
// Sets the value of the common name key.
SearchBuilder& SetCommonName(const std::string& value) {
common_name_ = value;
return *this;
}
// Sets the value of the userAccountControl key.
SearchBuilder& SetUserAccountControl(const uint32_t value) {
user_account_control_ = value;
return *this;
}
// Sets the value of the pwdLastSet key.
SearchBuilder& SetPwdLastSet(const uint64_t value) {
pwd_last_set_ = value;
return *this;
}
private:
std::string given_name_ = kGivenName;
std::string display_name_ = kDisplayName;
std::string object_guid_ = kAccountId;
std::string sam_account_name_ = kUserName;
std::string common_name_ = kCommonName;
uint32_t user_account_control_ = kUserAccountControl;
uint64_t pwd_last_set_ = kPwdLastSet;
};
// Searches |str| for (|searchKey|=value) and returns value. Returns an empty
// string if the key could not be found or if the value is empty.
std::string FindSearchValue(const std::string& str, const char* search_key) {
const std::string full_key = base::StringPrintf("(%s=", search_key);
size_t idx1 = str.find(full_key);
if (idx1 == std::string::npos)
return "";
const size_t idx2 = str.find(")", idx1 + full_key.size());
if (idx2 == std::string::npos)
return "";
idx1 += full_key.size();
return str.substr(idx1, idx2 - idx1);
}
// Prints custom stub net ads gpo list output corresponding to one remote GPO
// with the given properties. For |gpflags| see kGpFlag*.
std::string PrintGpo(const char* guid,
uint32_t version_user,
uint32_t version_machine,
int gpflags) {
DCHECK(gpflags >= 0 && gpflags < kGpFlagCount);
return base::StringPrintf(
kStubRemoteGpo, guid, (version_user << 16) | version_machine,
version_user, version_machine, version_user, version_user,
version_machine, version_machine, guid, guid, kGpFlagsStr[gpflags]);
}
// Writes a fake keytab file.
void WriteKeytabFile() {
std::string keytab_path = GetKeytabFilePath();
CHECK(!keytab_path.empty());
// Note: base::WriteFile triggers a seccomp failure, so do it old-school.
base::ScopedFILE kt_file(fopen(keytab_path.c_str(), "w"));
CHECK(kt_file);
CHECK_EQ(1U, fwrite(kStubKeytab, strlen(kStubKeytab), 1, kt_file.get()));
}
// Reads the smb.conf file at |smb_conf_path| and extracts the netbios name.
std::string GetMachineNameFromSmbConf(const std::string& smb_conf_path) {
std::string smb_conf;
CHECK(base::ReadFileToString(base::FilePath(smb_conf_path), &smb_conf));
std::string machine_name;
CHECK(FindToken(smb_conf, '=', "netbios name", &machine_name));
return machine_name;
}
// Returns different stub net ads search results depending on |object_guid|.
std::string GetSearchResultFromObjectGUID(const std::string& object_guid) {
SearchBuilder search_builder;
search_builder.SetObjectGuid(object_guid);
// Valid account id, return valid search result for the default user.
if (object_guid == kAccountId)
return search_builder.GetResult();
// Invalid account id, return bad "nothing found" search result.
if (object_guid == kBadAccountId)
return kStubBadSearch;
// Pretend that the password expired.
if (object_guid == kExpiredPasswordAccountId)
return search_builder.SetPwdLastSet(0).GetResult();
// Pretend that the password never expires.
if (object_guid == kNeverExpirePasswordAccountId) {
return search_builder.SetPwdLastSet(0)
.SetUserAccountControl(UF_DONT_EXPIRE_PASSWD)
.GetResult();
}
// Pretend that the password changed on the server.
if (object_guid == kPasswordChangedAccountId)
return search_builder.SetPwdLastSet(kPwdLastSet + 1).GetResult();
NOTREACHED() << "UNHANDLED OBJECT GUID " << object_guid;
return std::string();
}
// Returns different stub net ads search results depending on
// |sam_account_name|.
std::string GetSearchResultFromSAMAccountName(
const std::string& sam_account_name) {
SearchBuilder search_builder;
search_builder.SetSAMAccountName(sam_account_name);
// Set a special |kPasswordChangedAccountId|, required during auth for a
// test that uses that id in GetUserStatus().
if (sam_account_name == kPasswordChangedUserName)
return search_builder.SetObjectGuid(kPasswordChangedAccountId).GetResult();
// In all cases, just return a search result with the proper sAMAccountName.
return search_builder.GetResult();
}
// Handles a stub 'net ads workgroup' call. Just returns a fake workgroup.
int HandleWorkgroup() {
WriteOutput("Workgroup: WOKGROUP", "");
return kExitCodeOk;
}
// Handles a stub 'net ads join' call. Different behavior is triggered by
// passing different user principals, passwords and machine names (in smb.conf).
int HandleJoin(const std::string& command_line,
const std::string& smb_conf_path) {
// Read the password from stdin.
std::string password;
if (!ReadPipeToString(STDIN_FILENO, &password)) {
LOG(ERROR) << "Failed to read password";
return kExitCodeError;
}
const std::string kUserFlag = "-U ";
// Read machine name from smb.conf.
const std::string machine_name = GetMachineNameFromSmbConf(smb_conf_path);
CHECK(!machine_name.empty());
// Stub too long machine name error.
if (machine_name.size() > kMaxMachineNameSize) {
WriteOutput(
base::StringPrintf(kMachineNameTooLongError, kMaxMachineNameSize,
machine_name.c_str(), machine_name.size()),
"");
return kExitCodeError;
}
// Stub bad machine name error.
if (machine_name == base::ToUpperASCII(kInvalidMachineName)) {
WriteOutput(kInvalidMachineNameError, "");
return kExitCodeError;
}
// Stub insufficient quota error.
if (Contains(command_line, kUserFlag + kInsufficientQuotaUserPrincipal)) {
WriteOutput(kInsufficientQuotaError, "");
return kExitCodeError;
}
// Stub non-existing account error (same error as 'wrong password' error).
if (Contains(command_line, kUserFlag + kNonExistingUserPrincipal)) {
WriteOutput(kWrongPasswordError, "");
return kExitCodeError;
}
// Stub network error.
if (Contains(command_line, kUserFlag + kNetworkErrorUserPrincipal)) {
WriteOutput("", kNetworkError);
return kExitCodeError;
}
// Stub access denied error.
if (Contains(command_line, kUserFlag + kAccessDeniedUserPrincipal)) {
WriteOutput(kJoinAccessDeniedError, "");
return kExitCodeError;
}
// Stub valid user principal. Switch behavior based on password.
if (Contains(command_line, kUserFlag + kUserPrincipal)) {
// Stub wrong password.
if (password == kWrongPassword) {
WriteOutput(kWrongPasswordError, "");
return kExitCodeError;
}
// Stub expired password.
if (password == kExpiredPassword) {
WriteOutput(kExpiredPasswordError, "");
return kExitCodeError;
}
// Stub valid password.
if (password == kPassword) {
WriteKeytabFile();
return kExitCodeOk;
}
NOTREACHED() << "UNHANDLED PASSWORD " << password;
return kExitCodeError;
}
NOTREACHED() << "UNHANDLED COMMAND LINE " << command_line;
return kExitCodeError;
}
// Handles a stub 'net ads info' call. Just returns stub information.
int HandleInfo() {
WriteOutput(kStubInfo, "");
return kExitCodeOk;
}
// Handles a stub 'net ads gpo list' call. Different behavior is triggered by
// passing different machine names (in smb.conf).
int HandleGpoList(const std::string& smb_conf_path) {
// Read machine name from smb.conf.
const std::string machine_name = GetMachineNameFromSmbConf(smb_conf_path);
CHECK(!machine_name.empty());
// Stub empty GPO list.
if (machine_name == base::ToUpperASCII(kEmptyGpoMachineName))
return kExitCodeOk;
// All other GPO lists use the local GPO.
std::string gpos = kStubLocalGpo;
if (machine_name == base::ToUpperASCII(kGpoDownloadErrorMachineName)) {
// Stub GPO list that triggers a download error in smbclient.
gpos += PrintGpo(kErrorGpoGuid, 1, 1, kGpFlagAllEnabled);
} else if (machine_name == base::ToUpperASCII(kOneGpoMachineName)) {
// Stub GPO list that downloads one GPO if present.
gpos += PrintGpo(kGpo1Guid, 1, 1, kGpFlagAllEnabled);
} else if (machine_name == base::ToUpperASCII(kTwoGposMachineName)) {
// Stub GPO list that downloads two GPOs if present.
gpos += PrintGpo(kGpo1Guid, 1, 1, kGpFlagAllEnabled);
gpos += PrintGpo(kGpo2Guid, 1, 1, kGpFlagAllEnabled);
} else if (machine_name == base::ToUpperASCII(kZeroUserVersionMachineName)) {
// Stub GPO list that contains a GPO with version_user == 0 (should be
// ignored during user policy fetch).
gpos += PrintGpo(kGpo1Guid, 0, 1, kGpFlagAllEnabled);
} else if (machine_name == base::ToUpperASCII(kDisableUserFlagMachineName)) {
// Stub GPO list that contains a GPO with kGpFlagUserDisabled set (should be
// ignored during user policy fetch).
gpos += PrintGpo(kGpo1Guid, 1, 1, kGpFlagUserDisabled);
}
WriteOutput("", gpos);
return kExitCodeOk;
}
// Handles a stub 'net ads search' call. Different behavior is triggered by
// passing different sAMAccountNames or objectGUIDs as search term.
int HandleSearch(const std::string& command_line) {
std::string sam_account_name =
FindSearchValue(command_line, kSearchSAMAccountName);
std::string object_guid_octet =
FindSearchValue(command_line, kSearchObjectGUID);
std::string search_result;
if (!object_guid_octet.empty()) {
// Search by objectGUID aka account id.
std::string object_guid = OctetStringToGuidForTesting(object_guid_octet);
search_result = GetSearchResultFromObjectGUID(object_guid);
} else if (!sam_account_name.empty()) {
// Search by sAMAccountName.
search_result = GetSearchResultFromSAMAccountName(sam_account_name);
} else {
LOG(ERROR) << "SEARCH TERM NOT RECOGNIZED IN COMMAND LINE " << command_line;
}
WriteOutput(search_result, "");
return kExitCodeOk;
}
int HandleCommandLine(const std::string& command_line,
const std::string& smb_conf_path) {
// Make sure the caller adds the debug level.
CHECK(Contains(command_line, " -d "));
// Stub net ads workgroup.
if (StartsWithCaseSensitive(command_line, "ads workgroup"))
return HandleWorkgroup();
// Stub net ads join.
if (StartsWithCaseSensitive(command_line, "ads join"))
return HandleJoin(command_line, smb_conf_path);
// Stub net ads info.
if (StartsWithCaseSensitive(command_line, "ads info"))
return HandleInfo();
// Stub net ads gpo list.
if (StartsWithCaseSensitive(command_line, "ads gpo list"))
return HandleGpoList(smb_conf_path);
// Stub net ads search.
if (StartsWithCaseSensitive(command_line, "ads search"))
return HandleSearch(command_line);
NOTREACHED() << "UNHANDLED COMMAND LINE " << command_line;
return kExitCodeError;
}
} // namespace
} // namespace authpolicy
int main(int argc, char* argv[]) {
// Find Samba configuration path ("-s" argument).
const std::string smb_conf_path = authpolicy::GetArgValue(argc, argv, "-s");
if (smb_conf_path.empty()) {
authpolicy::WriteOutput("", authpolicy::kSmbConfArgMissingError);
return authpolicy::kExitCodeError;
}
const std::string command_line = authpolicy::GetCommandLine(argc, argv);
return authpolicy::HandleCommandLine(command_line, smb_conf_path);
}