Add support for user locked state property

Support for user locked state property using
pam_tally2 application added.

Change-Id: Ia77ff6527c15c93ac272110950e99fff56dcbaa6
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/user_mgr.cpp b/user_mgr.cpp
index ee72b05..786a8fd 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -25,6 +25,8 @@
 #include <algorithm>
 #include <numeric>
 #include <boost/process/child.hpp>
+#include <boost/process/io.hpp>
+#include <boost/algorithm/string/split.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/User/Common/error.hpp>
 #include <phosphor-logging/log.hpp>
@@ -82,18 +84,32 @@
 using Argument = xyz::openbmc_project::Common::InvalidArgument;
 
 template <typename... ArgTypes>
-static void executeCmd(const char *path, ArgTypes &&... tArgs)
+static std::vector<std::string> executeCmd(const char *path,
+                                           ArgTypes &&... tArgs)
 {
-    boost::process::child execProg(path, const_cast<char *>(tArgs)...);
+    std::vector<std::string> stdOutput;
+    boost::process::ipstream stdOutStream;
+    boost::process::child execProg(path, const_cast<char *>(tArgs)...,
+                                   boost::process::std_out > stdOutStream);
+    std::string stdOutLine;
+
+    while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
+           !stdOutLine.empty())
+    {
+        stdOutput.emplace_back(stdOutLine);
+    }
+
     execProg.wait();
+
     int retCode = execProg.exit_code();
     if (retCode)
     {
-        log<level::ERR>("Command execution failed", entry("PATH=%s", path),
+        log<level::ERR>("Command execution failed", entry("PATH=%d", path),
                         entry("RETURN_CODE:%d", retCode));
         elog<InternalFailure>();
     }
-    return;
+
+    return stdOutput;
 }
 
 static std::string getCSVFromVector(std::vector<std::string> vec)
@@ -631,6 +647,87 @@
     return;
 }
 
+/**
+ * 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
+ * [4] - failure app
+ **/
+
+static constexpr size_t t2UserIdx = 0;
+static constexpr size_t t2FailCntIdx = 1;
+static constexpr size_t t2OutputIndex = 1;
+
+bool UserMgr::userLockedForFailedAttempt(const std::string &userName)
+{
+    // All user management lock has to be based on /etc/shadow
+    phosphor::user::shadow::Lock lock();
+    std::vector<std::string> output;
+
+    output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
+
+    std::vector<std::string> splitWords;
+    boost::algorithm::split(splitWords, output[t2OutputIndex],
+                            boost::algorithm::is_any_of("\t "),
+                            boost::token_compress_on);
+
+    if (splitWords[t2UserIdx] == userName)
+    {
+        try
+        {
+            unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
+            uint16_t value16 = 0;
+            if (tmp > std::numeric_limits<decltype(value16)>::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
+        }
+        catch (const std::exception &e)
+        {
+            log<level::ERR>("Exception for userLockedForFailedAttempt",
+                            entry("WHAT=%s", e.what()));
+            throw e;
+        }
+    }
+    log<level::ERR>("Unable to get user account failed attempt",
+                    entry("USER_NAME=%s", userName.c_str()));
+    elog<InternalFailure>();
+    return false;
+}
+
+bool UserMgr::userLockedForFailedAttempt(const std::string &userName,
+                                         const bool &value)
+{
+    // All user management lock has to be based on /etc/shadow
+    phosphor::user::shadow::Lock lock();
+    std::vector<std::string> output;
+    if (value == true)
+    {
+        return userLockedForFailedAttempt(userName);
+    }
+    output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
+
+    std::vector<std::string> splitWords;
+    boost::algorithm::split(splitWords, output[t2OutputIndex],
+                            boost::algorithm::is_any_of("\t "),
+                            boost::token_compress_on);
+
+    if (splitWords[t2UserIdx] == userName)
+    {
+        return userLockedForFailedAttempt(userName);
+    }
+    log<level::ERR>("Unable to clear user account failed attempt");
+    elog<InternalFailure>();
+    return false;
+}
+
 UserSSHLists UserMgr::getUserAndSshGrpList()
 {
     // All user management lock has to be based on /etc/shadow
diff --git a/user_mgr.hpp b/user_mgr.hpp
index b599724..c1673f1 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -126,6 +126,21 @@
      */
     uint32_t accountUnlockTimeout(uint32_t val) override;
 
+    /** @brief lists user locked state for failed attempt
+     *
+     * @param[in] - user name
+     * @return - true / false indicating user locked / un-locked
+     **/
+    bool userLockedForFailedAttempt(const std::string &userName);
+
+    /** @brief lists user locked state for failed attempt
+     *
+     * @param[in]: user name
+     * @param[in]: value - false -unlock user account, true - no action taken
+     **/
+    bool userLockedForFailedAttempt(const std::string &userName,
+                                    const bool &value);
+
   private:
     /** @brief sdbusplus handler */
     sdbusplus::bus::bus &bus;
diff --git a/users.cpp b/users.cpp
index c904916..84c401d 100644
--- a/users.cpp
+++ b/users.cpp
@@ -142,5 +142,29 @@
     return UsersIface::userEnabled(value);
 }
 
+/** @brief lists user locked state for failed attempt
+ *
+ **/
+bool Users::userLockedForFailedAttempt(void) const
+{
+    return manager.userLockedForFailedAttempt(userName);
+}
+
+/** @brief unlock user locked state for failed attempt
+ *
+ * @param[in]: value - false - unlock user account, true - no action taken
+ **/
+bool Users::userLockedForFailedAttempt(bool value)
+{
+    if (value != false)
+    {
+        return userLockedForFailedAttempt();
+    }
+    else
+    {
+        return manager.userLockedForFailedAttempt(userName, value);
+    }
+}
+
 } // namespace user
 } // namespace phosphor
diff --git a/users.hpp b/users.hpp
index 84a0f86..84a32f5 100644
--- a/users.hpp
+++ b/users.hpp
@@ -102,6 +102,17 @@
      */
     bool userEnabled(bool value) override;
 
+    /** @brief lists user locked state for failed attempt
+     *
+     **/
+    bool userLockedForFailedAttempt(void) const override;
+
+    /** @brief unlock user locked state for failed attempt
+     *
+     * @param[in]: value - false - unlock user account, true - no action taken
+     **/
+    bool userLockedForFailedAttempt(bool value) override;
+
   private:
     std::string userName;
     UserMgr &manager;