Change to pam_faillock and pam pwquality
pam_tally2 is being replaced by pam_faillock. The parameters in
common-auth have moved to faillock.conf, so this commit adds a new
method to modify paramters in a given configuration file.
The output from the 'faillock' command differs from 'pam_tally2', so
this commit adds a new function to parse the output from 'faillock' to
determine if the user is currently locked.
pam_cracklib is being replaced by pam_pwquality. The parameters in
common-password have moved to pwquality.conf.
I referenced the work done by Joseph Reynolds in this commit [1] to know
what changes were required.
[1]: https://gerrit.openbmc.org/c/openbmc/phosphor-user-manager/+/39853
Tested:
Confirmed that the AccountLockoutDuration and AccountLockoutThreshold
parameters under /redfish/v1/AccountService both return the correct
value from common-auth.
Set deny to 10 and unlock_time to 30 seconds and confirmed that a user
account will correctly show as locked after 10 failed login attempts,
and that user will show as unlocked 30 seconds later.
Used Redfish to PATCH both AccountLockoutDuration and
AccountLockoutThreshold and confirmed that the updated values are
correctly reported in Redfish and that the correct lines in
faillock.conf are modified.
Confirmed that the MinPasswordLength parameter under
/redfish/v1/AccountService returns the correct value from
common-password.
Set minlen to 9 and confirmed that a user password could not be set with
a length of 8.
Used Redfish to PATCH MinPasswordLength and confirmed that the updated
value is correctly reported in Redfish and that the correct line in
pwquality.conf is modified.
Change-Id: I0701e4148c0b8333c6b8889d4695e61ce7f5366d
Signed-off-by: Jason M. Bills <jason.m.bills@intel.com>
diff --git a/test/user_mgr_test.cpp b/test/user_mgr_test.cpp
index e757803..40751e9 100644
--- a/test/user_mgr_test.cpp
+++ b/test/user_mgr_test.cpp
@@ -202,7 +202,7 @@
{
inline constexpr const char* objectRootInTest = "/xyz/openbmc_project/user";
-// Fake config; referenced config on real BMC
+// Fake configs; referenced configs on real BMC
inline constexpr const char* rawConfig = R"(
#
# /etc/pam.d/common-password - password-related modules common to all services
@@ -222,8 +222,8 @@
# See the pam_unix manpage for other options.
# here are the per-package modules (the "Primary" block)
-password [success=ok default=die] pam_tally2.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 deny=2 unlock_time=3 #some comments
-password [success=ok default=die] pam_cracklib.so debug enforce_for_root reject_username minlen=8 difok=0 lcredit=0 ocredit=0 dcredit=0 ucredit=0 #some comments
+password [success=ok default=die] pam_faillock.so authsucc
+password [success=ok default=die] pam_pwquality.so debug
password [success=ok default=die] pam_ipmicheck.so spec_grp_name=ipmi use_authtok
password [success=ok ignore=ignore default=die] pam_pwhistory.so debug enforce_for_root remember=0 use_authtok
password [success=ok default=die] pam_unix.so sha512 use_authtok
@@ -236,6 +236,19 @@
password required pam_permit.so
# and here are more per-package modules (the "Additional" block)
)";
+inline constexpr const char* rawFailLockConfig = R"(
+deny=2
+unlock_time=3
+)";
+inline constexpr const char* rawPWQualityConfig = R"(
+enforce_for_root
+minlen=8
+difok=0
+lcredit=0
+ocredit=0
+dcredit=0
+ucredit=0
+)";
} // namespace
void dumpStringToFile(const std::string& str, const std::string& filePath)
@@ -263,9 +276,18 @@
tempPamConfigFile = "/tmp/test-data-XXXXXX";
mktemp(tempPamConfigFile.data());
EXPECT_NO_THROW(dumpStringToFile(rawConfig, tempPamConfigFile));
+ tempFaillockConfigFile = "/tmp/test-data-XXXXXX";
+ mktemp(tempFaillockConfigFile.data());
+ EXPECT_NO_THROW(
+ dumpStringToFile(rawFailLockConfig, tempFaillockConfigFile));
+ tempPWQualityConfigFile = "/tmp/test-data-XXXXXX";
+ mktemp(tempPWQualityConfigFile.data());
+ EXPECT_NO_THROW(
+ dumpStringToFile(rawPWQualityConfig, tempPWQualityConfigFile));
// Set config files to test files
pamPasswdConfigFile = tempPamConfigFile;
- pamAuthConfigFile = tempPamConfigFile;
+ faillockConfigFile = tempFaillockConfigFile;
+ pwQualityConfigFile = tempPWQualityConfigFile;
ON_CALL(*this, executeUserAdd).WillByDefault(testing::Return());
@@ -295,6 +317,8 @@
~UserMgrInTest() override
{
EXPECT_NO_THROW(removeFile(tempPamConfigFile));
+ EXPECT_NO_THROW(removeFile(tempFaillockConfigFile));
+ EXPECT_NO_THROW(removeFile(tempPWQualityConfigFile));
}
MOCK_METHOD(void, executeUserAdd, (const char*, const char*, bool, bool),
@@ -323,51 +347,110 @@
protected:
static sdbusplus::bus_t busInTest;
std::string tempPamConfigFile;
+ std::string tempFaillockConfigFile;
+ std::string tempPWQualityConfigFile;
};
sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
{
- std::string minLen;
- EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
- EXPECT_EQ(minLen, "8");
- EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
- EXPECT_EQ(minLen, "8");
+ std::string remember;
+ EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
+ 0);
+ EXPECT_EQ(remember, "0");
+}
+
+TEST_F(UserMgrInTest, GetPamModuleConfValueOnSuccess)
+{
+ std::string minlen;
+ EXPECT_EQ(getPamModuleConfValue(tempPWQualityConfigFile, "minlen", minlen),
+ 0);
+ EXPECT_EQ(minlen, "8");
+ std::string deny;
+ EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), 0);
+ EXPECT_EQ(deny, "2");
}
TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
{
- EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), 0);
- EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), 0);
- std::string minLen;
- EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), 0);
- EXPECT_EQ(minLen, "16");
- EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), 0);
- EXPECT_EQ(minLen, "16");
+ EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), 0);
+ std::string remember;
+ EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
+ 0);
+ EXPECT_EQ(remember, "1");
+}
+
+TEST_F(UserMgrInTest, SetPamModuleConfValueOnSuccess)
+{
+ EXPECT_EQ(setPamModuleConfValue(tempPWQualityConfigFile, "minlen", "16"),
+ 0);
+ std::string minlen;
+ EXPECT_EQ(getPamModuleConfValue(tempPWQualityConfigFile, "minlen", minlen),
+ 0);
+ EXPECT_EQ(minlen, "16");
+
+ EXPECT_EQ(setPamModuleConfValue(tempFaillockConfigFile, "deny", "3"), 0);
+ std::string deny;
+ EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), 0);
+ EXPECT_EQ(deny, "3");
}
TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
{
EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
- std::string minLen;
- EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
- EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
+ std::string remember;
+ EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
+ -1);
EXPECT_NO_THROW(removeFile(tempPamConfigFile));
- EXPECT_EQ(getPamModuleArgValue("pam_tally2.so", "minlen", minLen), -1);
- EXPECT_EQ(getPamModuleArgValue("pam_cracklib.so", "minlen", minLen), -1);
+ EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
+ -1);
+}
+
+TEST_F(UserMgrInTest, GetPamModuleConfValueOnFailure)
+{
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWQualityConfigFile));
+ std::string minlen;
+ EXPECT_EQ(getPamModuleConfValue(tempPWQualityConfigFile, "minlen", minlen),
+ -1);
+
+ EXPECT_NO_THROW(removeFile(tempPWQualityConfigFile));
+ EXPECT_EQ(getPamModuleConfValue(tempPWQualityConfigFile, "minlen", minlen),
+ -1);
+
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempFaillockConfigFile));
+ std::string deny;
+ EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), -1);
+
+ EXPECT_NO_THROW(removeFile(tempFaillockConfigFile));
+ EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), -1);
}
TEST_F(UserMgrInTest, SetPamModuleArgValueOnFailure)
{
EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
- EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
- EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
+ EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), -1);
EXPECT_NO_THROW(removeFile(tempPamConfigFile));
- EXPECT_EQ(setPamModuleArgValue("pam_cracklib.so", "minlen", "16"), -1);
- EXPECT_EQ(setPamModuleArgValue("pam_tally2.so", "minlen", "16"), -1);
+ EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), -1);
+}
+
+TEST_F(UserMgrInTest, SetPamModuleConfValueOnFailure)
+{
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWQualityConfigFile));
+ EXPECT_EQ(setPamModuleConfValue(tempPWQualityConfigFile, "minlen", "16"),
+ -1);
+
+ EXPECT_NO_THROW(removeFile(tempPWQualityConfigFile));
+ EXPECT_EQ(setPamModuleConfValue(tempPWQualityConfigFile, "minlen", "16"),
+ -1);
+
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempFaillockConfigFile));
+ EXPECT_EQ(setPamModuleConfValue(tempFaillockConfigFile, "deny", "3"), -1);
+
+ EXPECT_NO_THROW(removeFile(tempFaillockConfigFile));
+ EXPECT_EQ(setPamModuleConfValue(tempFaillockConfigFile, "deny", "3"), -1);
}
TEST_F(UserMgrInTest, IsUserExistEmptyInputThrowsError)
@@ -616,7 +699,7 @@
TEST_F(UserMgrInTest, MinPasswordLengthOnFailure)
{
- EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWQualityConfigFile));
initializeAccountPolicy();
EXPECT_EQ(AccountPolicyIface::minPasswordLength(), 8);
EXPECT_THROW(
@@ -672,12 +755,12 @@
TEST_F(UserMgrInTest, MaxLoginAttemptBeforeLockoutOnFailure)
{
- EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
initializeAccountPolicy();
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempFaillockConfigFile));
EXPECT_THROW(
UserMgr::maxLoginAttemptBeforeLockout(16),
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
- EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
+ EXPECT_EQ(AccountPolicyIface::maxLoginAttemptBeforeLockout(), 2);
}
TEST_F(UserMgrInTest, AccountUnlockTimeoutReturnsIfValueIsTheSame)
@@ -699,7 +782,7 @@
TEST_F(UserMgrInTest, AccountUnlockTimeoutOnFailure)
{
initializeAccountPolicy();
- EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
+ EXPECT_NO_THROW(dumpStringToFile("whatever", tempFaillockConfigFile));
EXPECT_THROW(
UserMgr::accountUnlockTimeout(16),
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
@@ -757,10 +840,11 @@
std::string username = "user001";
initializeAccountPolicy();
// Example output from BMC
- // root@s7106:~# pam_tally2 -u root
- // Login Failures Latest failure From
- // root 0
- std::vector<std::string> output = {"whatever", "root\t0"};
+ // root:~# faillock --user root
+ // root:
+ // When Type Source Valid
+ std::vector<std::string> output = {"whatever",
+ "When Type Source Valid"};
EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
.WillOnce(testing::Return(output));
@@ -781,34 +865,6 @@
}
TEST_F(UserMgrInTest,
- UserLockedForFailedAttemptThrowsInternalFailureIfFailAttemptsOutOfRange)
-{
- std::string username = "user001";
- initializeAccountPolicy();
- std::vector<std::string> output = {"whatever", "root\t1000000"};
- EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
- .WillOnce(testing::Return(output));
-
- EXPECT_THROW(
- userLockedForFailedAttempt(username),
- sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
-}
-
-TEST_F(UserMgrInTest,
- UserLockedForFailedAttemptThrowsInternalFailureIfNoFailDateTime)
-{
- std::string username = "user001";
- initializeAccountPolicy();
- std::vector<std::string> output = {"whatever", "root\t2"};
- EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
- .WillOnce(testing::Return(output));
-
- EXPECT_THROW(
- userLockedForFailedAttempt(username),
- sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure);
-}
-
-TEST_F(UserMgrInTest,
UserLockedForFailedAttemptThrowsInternalFailureIfWrongDateFormat)
{
std::string username = "user001";
@@ -816,7 +872,7 @@
// Choose a date in the past.
std::vector<std::string> output = {"whatever",
- "root\t2\t10/24/2002\t00:00:00"};
+ "10/24/2002 00:00:00 type source V"};
EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
.WillOnce(testing::Return(output));
@@ -833,7 +889,7 @@
// Choose a date in the past.
std::vector<std::string> output = {"whatever",
- "root\t2\t10/24/02\t00:00:00"};
+ "2002-10-24 00:00:00 type source V"};
EXPECT_CALL(*this, getFailedAttempt(testing::StrEq(username.c_str())))
.WillOnce(testing::Return(output));
diff --git a/user_mgr.cpp b/user_mgr.cpp
index 090e204..61d2224 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -61,8 +61,6 @@
static constexpr int failure = -1;
// pam modules related
-static constexpr const char* pamTally2 = "pam_tally2.so";
-static constexpr const char* pamCrackLib = "pam_cracklib.so";
static constexpr const char* pamPWHistory = "pam_pwhistory.so";
static constexpr const char* minPasswdLenProp = "minlen";
static constexpr const char* remOldPasswdCount = "remember";
@@ -70,8 +68,10 @@
static constexpr const char* unlockTimeout = "unlock_time";
static constexpr const char* defaultPamPasswdConfigFile =
"/etc/pam.d/common-password";
-static constexpr const char* defaultPamAuthConfigFile =
- "/etc/pam.d/common-auth";
+static constexpr const char* defaultFaillockConfigFile =
+ "/etc/security/faillock.conf";
+static constexpr const char* defaultPWQualityConfigFile =
+ "/etc/security/pwquality.conf";
// Object Manager related
static constexpr const char* ldapMgrObjBasePath =
@@ -577,8 +577,8 @@
Argument::ARGUMENT_NAME("minPasswordLength"),
Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
}
- if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
- std::to_string(value)) != success)
+ if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
+ std::to_string(value)) != success)
{
lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
value);
@@ -609,8 +609,8 @@
{
return value;
}
- if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
- std::to_string(value)) != success)
+ if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
+ std::to_string(value)) != success)
{
lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
"VALUE", value);
@@ -625,8 +625,8 @@
{
return value;
}
- if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
- success)
+ if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
+ std::to_string(value)) != success)
{
lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
value);
@@ -639,15 +639,7 @@
const std::string& argName,
std::string& argValue)
{
- std::string fileName;
- if (moduleName == pamTally2)
- {
- fileName = pamAuthConfigFile;
- }
- else
- {
- fileName = pamPasswdConfigFile;
- }
+ std::string fileName = pamPasswdConfigFile;
std::ifstream fileToRead(fileName, std::ios::in);
if (!fileToRead.is_open())
{
@@ -688,19 +680,52 @@
return failure;
}
+int UserMgr::getPamModuleConfValue(const std::string& confFile,
+ const std::string& argName,
+ std::string& argValue)
+{
+ std::ifstream fileToRead(confFile, std::ios::in);
+ if (!fileToRead.is_open())
+ {
+ lg2::error("Failed to open pam configuration file {FILENAME}",
+ "FILENAME", confFile);
+ return failure;
+ }
+ std::string line;
+ auto argSearch = argName + "=";
+ size_t startPos = 0;
+ size_t endPos = 0;
+ while (getline(fileToRead, line))
+ {
+ // skip comments section starting with #
+ if ((startPos = line.find('#')) != std::string::npos)
+ {
+ if (startPos == 0)
+ {
+ continue;
+ }
+ // skip comments after meaningful section and process those
+ line = line.substr(0, startPos);
+ }
+ if ((startPos = line.find(argSearch)) != std::string::npos)
+ {
+ if ((endPos = line.find(' ', startPos)) == std::string::npos)
+ {
+ endPos = line.size();
+ }
+ startPos += argSearch.size();
+ argValue = line.substr(startPos, endPos - startPos);
+ return success;
+ }
+ }
+ return failure;
+}
+
int UserMgr::setPamModuleArgValue(const std::string& moduleName,
const std::string& argName,
const std::string& argValue)
{
- std::string fileName;
- if (moduleName == pamTally2)
- {
- fileName = pamAuthConfigFile;
- }
- else
- {
- fileName = pamPasswdConfigFile;
- }
+ std::string fileName = pamPasswdConfigFile;
std::string tmpFileName = fileName + "_tmp";
std::ifstream fileToRead(fileName, std::ios::in);
std::ofstream fileToWrite(tmpFileName, std::ios::out);
@@ -758,6 +783,64 @@
return failure;
}
+int UserMgr::setPamModuleConfValue(const std::string& confFile,
+ const std::string& argName,
+ const std::string& argValue)
+{
+ std::string tmpConfFile = confFile + "_tmp";
+ std::ifstream fileToRead(confFile, std::ios::in);
+ std::ofstream fileToWrite(tmpConfFile, std::ios::out);
+ if (!fileToRead.is_open() || !fileToWrite.is_open())
+ {
+ lg2::error("Failed to open pam configuration file {FILENAME}",
+ "FILENAME", confFile);
+ return failure;
+ }
+ std::string line;
+ auto argSearch = argName + "=";
+ size_t startPos = 0;
+ size_t endPos = 0;
+ bool found = false;
+ while (getline(fileToRead, line))
+ {
+ // skip comments section starting with #
+ if ((startPos = line.find('#')) != std::string::npos)
+ {
+ if (startPos == 0)
+ {
+ fileToWrite << line << std::endl;
+ continue;
+ }
+ // skip comments after meaningful section and process those
+ line = line.substr(0, startPos);
+ }
+ if ((startPos = line.find(argSearch)) != std::string::npos)
+ {
+ if ((endPos = line.find(' ', startPos)) == std::string::npos)
+ {
+ endPos = line.size();
+ }
+ startPos += argSearch.size();
+ fileToWrite << line.substr(0, startPos) << argValue
+ << line.substr(endPos, line.size() - endPos)
+ << std::endl;
+ found = true;
+ continue;
+ }
+ fileToWrite << line << std::endl;
+ }
+ fileToWrite.close();
+ fileToRead.close();
+ if (found)
+ {
+ if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
+ {
+ return success;
+ }
+ }
+ return failure;
+}
+
void UserMgr::userEnable(const std::string& userName, bool enabled)
{
// All user management lock has to be based on /etc/shadow
@@ -780,16 +863,75 @@
}
/**
- * pam_tally2 app will provide the user failure count and failure status
- * in second line of output with words position [0] - user name,
- * [1] - failure count, [2] - latest failure date, [3] - latest failure time
- * [4] - failure app
+ * faillock app will provide the user failed login list with when the attempt
+ * was made, the type, the source, and if it's valid.
+ *
+ * Valid in this case means that the attempt was made within the fail_interval
+ * time. So, we can check this list for the number of valid entries (lines
+ * ending with 'V') compared to the maximum allowed to determine if the user is
+ * locked out.
+ *
+ * This data is only refreshed when an attempt is made, so if the user appears
+ * to be locked out, we must also check if the most recent attempt was older
+ * than the unlock_time to know if the user has since been unlocked.
**/
+bool UserMgr::parseFaillockForLockout(
+ const std::vector<std::string>& faillockOutput)
+{
+ uint16_t failAttempts = 0;
+ time_t lastFailedAttempt{};
+ for (const std::string& line : faillockOutput)
+ {
+ if (!line.ends_with("V"))
+ {
+ continue;
+ }
-static constexpr size_t t2FailCntIdx = 1;
-static constexpr size_t t2FailDateIdx = 2;
-static constexpr size_t t2FailTimeIdx = 3;
-static constexpr size_t t2OutputIndex = 1;
+ // Count this failed attempt
+ failAttempts++;
+
+ // Update the last attempt time
+ // First get the "when" which is the first two words (date and time)
+ size_t pos = line.find(" ");
+ if (pos == std::string::npos)
+ {
+ continue;
+ }
+ pos = line.find(" ", pos + 1);
+ if (pos == std::string::npos)
+ {
+ continue;
+ }
+ std::string failDateTime = line.substr(0, pos);
+
+ // NOTE: Cannot use std::get_time() here as the implementation of %y in
+ // libstdc++ does not match POSIX strptime() before gcc 12.1.0
+ // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
+ std::tm tmStruct = {};
+ if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
+ {
+ lg2::error("Failed to parse latest failure date/time");
+ elog<InternalFailure>();
+ }
+
+ time_t failTimestamp = std::mktime(&tmStruct);
+ lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
+ }
+
+ if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
+ {
+ return false;
+ }
+
+ if (lastFailedAttempt +
+ static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
+ std::time(NULL))
+ {
+ return false;
+ }
+
+ return true;
+}
bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
{
@@ -811,61 +953,7 @@
elog<InternalFailure>();
}
- std::vector<std::string> splitWords;
- boost::algorithm::split(splitWords, output[t2OutputIndex],
- boost::algorithm::is_any_of("\t "),
- boost::token_compress_on);
- uint16_t failAttempts = 0;
- try
- {
- unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
- if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
- {
- throw std::out_of_range("Out of range");
- }
- failAttempts = static_cast<decltype(failAttempts)>(tmp);
- }
- catch (const std::exception& e)
- {
- lg2::error("Exception for userLockedForFailedAttempt: {ERR}", "ERR", e);
- elog<InternalFailure>();
- }
-
- if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
- {
- return false;
- }
-
- // When failedAttempts is not 0, Latest failure date/time should be
- // available
- if (splitWords.size() < 4)
- {
- lg2::error("Unable to read latest failure date/time");
- elog<InternalFailure>();
- }
-
- const std::string failDateTime = splitWords[t2FailDateIdx] + ' ' +
- splitWords[t2FailTimeIdx];
-
- // NOTE: Cannot use std::get_time() here as the implementation of %y in
- // libstdc++ does not match POSIX strptime() before gcc 12.1.0
- // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
- std::tm tmStruct = {};
- if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
- {
- lg2::error("Failed to parse latest failure date/time");
- elog<InternalFailure>();
- }
-
- time_t failTimestamp = std::mktime(&tmStruct);
- if (failTimestamp +
- static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
- std::time(NULL))
- {
- return false;
- }
-
- return true;
+ return parseFaillockForLockout(output);
}
bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
@@ -880,7 +968,7 @@
try
{
- executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
+ executeCmd("/usr/sbin/faillock", "--user", userName.c_str(), "--reset");
}
catch (const InternalFailure& e)
{
@@ -1319,8 +1407,8 @@
std::string valueStr;
auto value = minPasswdLength;
unsigned long tmp = 0;
- if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
- success)
+ if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
+ valueStr) != success)
{
AccountPolicyIface::minPasswordLength(minPasswdLength);
}
@@ -1369,7 +1457,8 @@
AccountPolicyIface::rememberOldPasswordTimes(value);
}
valueStr.clear();
- if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
+ if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
+ success)
{
AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
}
@@ -1394,7 +1483,8 @@
AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
}
valueStr.clear();
- if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
+ if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
+ success)
{
AccountPolicyIface::accountUnlockTimeout(0);
}
@@ -1488,7 +1578,8 @@
UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
pamPasswdConfigFile(defaultPamPasswdConfigFile),
- pamAuthConfigFile(defaultPamAuthConfigFile)
+ faillockConfigFile(defaultFaillockConfigFile),
+ pwQualityConfigFile(defaultPWQualityConfigFile)
{
UserMgrIface::allPrivileges(privMgr);
groupsMgr = readAllGroupsOnSystem();
@@ -1541,7 +1632,7 @@
std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
{
- return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
+ return executeCmd("/usr/sbin/faillock", "--user", userName);
}
} // namespace user
diff --git a/user_mgr.hpp b/user_mgr.hpp
index 6dcc898..559ab8b 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -200,6 +200,14 @@
*/
uint32_t accountUnlockTimeout(uint32_t val) override;
+ /** @brief parses the faillock output for locked user status
+ *
+ * @param[in] - output from faillock for the user
+ * @return - true / false indicating user locked / un-locked
+ **/
+ bool
+ parseFaillockForLockout(const std::vector<std::string>& faillockOutput);
+
/** @brief lists user locked state for failed attempt
*
* @param[in] - user name
@@ -259,6 +267,20 @@
int getPamModuleArgValue(const std::string& moduleName,
const std::string& argName, std::string& argValue);
+ /** @brief get pam argument value
+ * method to get argument value from pam configuration
+ *
+ * @param[in] confFile - path of the module config file from where arg has
+ * to be read
+ * @param[in] argName - argument name
+ * @param[out] argValue - argument value
+ *
+ * @return 0 - success state of the function
+ */
+ int getPamModuleConfValue(const std::string& confFile,
+ const std::string& argName,
+ std::string& argValue);
+
/** @brief set pam argument value
* method to set argument value in pam configuration
*
@@ -273,6 +295,20 @@
const std::string& argName,
const std::string& argValue);
+ /** @brief set pam argument value
+ * method to set argument value in pam configuration
+ *
+ * @param[in] confFile - path of the module config file in which argument
+ * value has to be set
+ * @param[in] argName - argument name
+ * @param[out] argValue - argument value
+ *
+ * @return 0 - success state of the function
+ */
+ int setPamModuleConfValue(const std::string& confFile,
+ const std::string& argName,
+ const std::string& argValue);
+
/** @brief check for user presence
* method to check for user existence
*
@@ -452,7 +488,8 @@
friend class TestUserMgr;
std::string pamPasswdConfigFile;
- std::string pamAuthConfigFile;
+ std::string faillockConfigFile;
+ std::string pwQualityConfigFile;
};
} // namespace user