Check time when getting UserLockedForFailedAttempt
In pam_tally2, the user is unlocked after unlock_time seconds. But
phosphor-user-manager only checks the login failure attempts and sets
UserLockedForFailedAttempt to true when it exceeds the max attempts
allowed. This patch fixes this issue by adding a comparison of current
time and the last login failure time.
Tested:
Login with wrong password for {MaxLoginAttemptBeforeLockout} times,
verified UserLockedForFailedAttempt is changed to true. Then wait for
{AccountUnlockTimeout} seconds, it becomes to false.
Change-Id: If8427e5644e0cd88758b0266973feb50a9831fe1
Signed-off-by: Jiaqing Zhao <jiaqing.zhao@intel.com>
diff --git a/user_mgr.cpp b/user_mgr.cpp
index d5100af..61ffc05 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -40,6 +40,7 @@
#include <xyz/openbmc_project/User/Common/error.hpp>
#include <algorithm>
+#include <ctime>
#include <fstream>
#include <numeric>
#include <regex>
@@ -681,18 +682,25 @@
/**
* 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 timestamp, [3] - failure timestamp
+ * [1] - failure count, [2] - latest failure date, [3] - latest failure time
* [4] - failure app
**/
static constexpr size_t t2UserIdx = 0;
static constexpr size_t t2FailCntIdx = 1;
+static constexpr size_t t2FailDateIdx = 2;
+static constexpr size_t t2FailTimeIdx = 3;
static constexpr size_t t2OutputIndex = 1;
bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
{
// All user management lock has to be based on /etc/shadow
// TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
+ if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
+ {
+ return false;
+ }
+
std::vector<std::string> output;
try
{
@@ -709,28 +717,58 @@
boost::algorithm::is_any_of("\t "),
boost::token_compress_on);
+ uint16_t failAttempts = 0;
try
{
unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
- uint16_t value16 = 0;
- if (tmp > std::numeric_limits<decltype(value16)>::max())
+ if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
{
throw std::out_of_range("Out of range");
}
- value16 = static_cast<decltype(value16)>(tmp);
- if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
- value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
- {
- return true; // User account is locked out
- }
- return false; // User account is un-locked
+ failAttempts = static_cast<decltype(failAttempts)>(tmp);
}
catch (const std::exception& e)
{
log<level::ERR>("Exception for userLockedForFailedAttempt",
entry("WHAT=%s", e.what()));
- throw;
+ elog<InternalFailure>();
}
+
+ if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
+ {
+ return false;
+ }
+
+ // When failedAttempts is not 0, Latest failure date/time should be
+ // available
+ if (splitWords.size() < 4)
+ {
+ log<level::ERR>("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))
+ {
+ log<level::ERR>("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;
}
bool UserMgr::userLockedForFailedAttempt(const std::string& userName,