Update pam_pwhistory to use pwhistory.conf

With libpam 1.5.3, pam_pwhistory added support to get configuration
options from pwhistory.conf similar to faillock and pwquality.

This updates pam_pwhistory to use pwhistory.conf for the remember
setting.

pwhistory remember was the last setting directly using the
common-password file, so this also removes the two functions to get and
set PAM module arg values which resolves #16.

Tested:
Confirmed that getting and setting the RememberOldPasswordTimes D-Bus
property gets and sets the remember value in pwhistory.conf.

Change-Id: If8f90720c120c5c49e2b8b4a840a427d46ffc7d9
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 0a19918..9c825a4 100644
--- a/test/user_mgr_test.cpp
+++ b/test/user_mgr_test.cpp
@@ -203,43 +203,14 @@
 inline constexpr const char* objectRootInTest = "/xyz/openbmc_project/user";
 
 // 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
-#
-# This file is included from other service-specific PAM config files,
-# and should contain a list of modules that define the services to be
-# used to change user passwords.  The default is pam_unix.
-
-# Explanation of pam_unix options:
-#
-# The "sha512" option enables salted SHA512 passwords.  Without this option,
-# the default is Unix crypt.  Prior releases used the option "md5".
-#
-# The "obscure" option replaces the old `OBSCURE_CHECKS_ENAB' option in
-# login.defs.
-#
-# See the pam_unix manpage for other options.
-
-# here are the per-package modules (the "Primary" block)
-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
-password	[success=1 default=die] 	pam_ipmisave.so spec_grp_name=ipmi spec_pass_file=/etc/ipmi_pass key_file=/etc/key_file
-# here's the fallback if no module succeeds
-password	requisite			pam_deny.so
-# prime the stack with a positive return value if there isn't one already;
-# this avoids us returning an error just because nothing sets a success code
-# since the modules above will each just jump around
-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* rawPWHistoryConfig = R"(
+enforce_for_root
+remember=0
+)";
 inline constexpr const char* rawPWQualityConfig = R"(
 enforce_for_root
 minlen=8
@@ -273,20 +244,21 @@
   public:
     UserMgrInTest() : UserMgr(busInTest, objectRootInTest)
     {
-        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));
+        tempPWHistoryConfigFile = "/tmp/test-data-XXXXXX";
+        mktemp(tempPWHistoryConfigFile.data());
+        EXPECT_NO_THROW(
+            dumpStringToFile(rawPWHistoryConfig, tempPWHistoryConfigFile));
         tempPWQualityConfigFile = "/tmp/test-data-XXXXXX";
         mktemp(tempPWQualityConfigFile.data());
         EXPECT_NO_THROW(
             dumpStringToFile(rawPWQualityConfig, tempPWQualityConfigFile));
         // Set config files to test files
-        pamPasswdConfigFile = tempPamConfigFile;
         faillockConfigFile = tempFaillockConfigFile;
+        pwHistoryConfigFile = tempPWHistoryConfigFile;
         pwQualityConfigFile = tempPWQualityConfigFile;
 
         ON_CALL(*this, executeUserAdd).WillByDefault(testing::Return());
@@ -319,8 +291,8 @@
 
     ~UserMgrInTest() override
     {
-        EXPECT_NO_THROW(removeFile(tempPamConfigFile));
         EXPECT_NO_THROW(removeFile(tempFaillockConfigFile));
+        EXPECT_NO_THROW(removeFile(tempPWHistoryConfigFile));
         EXPECT_NO_THROW(removeFile(tempPWQualityConfigFile));
     }
 
@@ -351,21 +323,13 @@
 
   protected:
     static sdbusplus::bus_t busInTest;
-    std::string tempPamConfigFile;
     std::string tempFaillockConfigFile;
+    std::string tempPWHistoryConfigFile;
     std::string tempPWQualityConfigFile;
 };
 
 sdbusplus::bus_t UserMgrInTest::busInTest = sdbusplus::bus::new_default();
 
-TEST_F(UserMgrInTest, GetPamModuleArgValueOnSuccess)
-{
-    std::string remember;
-    EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
-              0);
-    EXPECT_EQ(remember, "0");
-}
-
 TEST_F(UserMgrInTest, GetPamModuleConfValueOnSuccess)
 {
     std::string minlen;
@@ -375,15 +339,11 @@
     std::string deny;
     EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), 0);
     EXPECT_EQ(deny, "2");
-}
-
-TEST_F(UserMgrInTest, SetPamModuleArgValueOnSuccess)
-{
-    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");
+    EXPECT_EQ(
+        getPamModuleConfValue(tempPWHistoryConfigFile, "remember", remember),
+        0);
+    EXPECT_EQ(remember, "0");
 }
 
 TEST_F(UserMgrInTest, SetPamModuleConfValueOnSuccess)
@@ -399,14 +359,14 @@
     std::string deny;
     EXPECT_EQ(getPamModuleConfValue(tempFaillockConfigFile, "deny", deny), 0);
     EXPECT_EQ(deny, "3");
-}
 
-TEST_F(UserMgrInTest, SetPamModuleArgValueTempFileOnSuccess)
-{
-    EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), 0);
-
-    std::string tmpFile = tempPamConfigFile + "_tmp";
-    EXPECT_FALSE(std::filesystem::exists(tmpFile));
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              0);
+    std::string remember;
+    EXPECT_EQ(
+        getPamModuleConfValue(tempPWHistoryConfigFile, "remember", remember),
+        0);
+    EXPECT_EQ(remember, "1");
 }
 
 TEST_F(UserMgrInTest, SetPamModuleConfValueTempFileOnSuccess)
@@ -421,18 +381,12 @@
 
     tmpFile = tempFaillockConfigFile + "_tmp";
     EXPECT_FALSE(std::filesystem::exists(tmpFile));
-}
 
-TEST_F(UserMgrInTest, GetPamModuleArgValueOnFailure)
-{
-    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
-    std::string remember;
-    EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
-              -1);
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              0);
 
-    EXPECT_NO_THROW(removeFile(tempPamConfigFile));
-    EXPECT_EQ(getPamModuleArgValue("pam_pwhistory.so", "remember", remember),
-              -1);
+    tmpFile = tempPWHistoryConfigFile + "_tmp";
+    EXPECT_FALSE(std::filesystem::exists(tmpFile));
 }
 
 TEST_F(UserMgrInTest, GetPamModuleConfValueOnFailure)
@@ -452,15 +406,17 @@
 
     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_pwhistory.so", "remember", "1"), -1);
+    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWHistoryConfigFile));
+    std::string remember;
+    EXPECT_EQ(
+        getPamModuleConfValue(tempPWHistoryConfigFile, "remember", remember),
+        -1);
 
-    EXPECT_NO_THROW(removeFile(tempPamConfigFile));
-    EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), -1);
+    EXPECT_NO_THROW(removeFile(tempPWHistoryConfigFile));
+    EXPECT_EQ(
+        getPamModuleConfValue(tempPWHistoryConfigFile, "remember", remember),
+        -1);
 }
 
 TEST_F(UserMgrInTest, SetPamModuleConfValueOnFailure)
@@ -478,20 +434,14 @@
 
     EXPECT_NO_THROW(removeFile(tempFaillockConfigFile));
     EXPECT_EQ(setPamModuleConfValue(tempFaillockConfigFile, "deny", "3"), -1);
-}
 
-TEST_F(UserMgrInTest, SetPamModuleArgValueTempFileOnFailure)
-{
-    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
-    EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), -1);
+    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWHistoryConfigFile));
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              -1);
 
-    std::string tmpFile = tempPamConfigFile + "_tmp";
-    EXPECT_FALSE(std::filesystem::exists(tmpFile));
-
-    EXPECT_NO_THROW(removeFile(tempPamConfigFile));
-    EXPECT_EQ(setPamModuleArgValue("pam_pwhistory.so", "remember", "1"), -1);
-
-    EXPECT_FALSE(std::filesystem::exists(tmpFile));
+    EXPECT_NO_THROW(removeFile(tempPWHistoryConfigFile));
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              -1);
 }
 
 TEST_F(UserMgrInTest, SetPamModuleConfValueTempFileOnFailure)
@@ -519,6 +469,19 @@
     EXPECT_EQ(setPamModuleConfValue(tempFaillockConfigFile, "deny", "3"), -1);
 
     EXPECT_FALSE(std::filesystem::exists(tmpFile));
+
+    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWHistoryConfigFile));
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              -1);
+
+    tmpFile = tempPWHistoryConfigFile + "_tmp";
+    EXPECT_FALSE(std::filesystem::exists(tmpFile));
+
+    EXPECT_NO_THROW(removeFile(tempPWHistoryConfigFile));
+    EXPECT_EQ(setPamModuleConfValue(tempPWHistoryConfigFile, "remember", "1"),
+              -1);
+
+    EXPECT_FALSE(std::filesystem::exists(tmpFile));
 }
 
 TEST_F(UserMgrInTest, IsUserExistEmptyInputThrowsError)
@@ -814,7 +777,7 @@
 
 TEST_F(UserMgrInTest, RememberOldPasswordTimesOnFailure)
 {
-    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPamConfigFile));
+    EXPECT_NO_THROW(dumpStringToFile("whatever", tempPWHistoryConfigFile));
     initializeAccountPolicy();
     EXPECT_EQ(AccountPolicyIface::rememberOldPasswordTimes(), 0);
     EXPECT_THROW(
diff --git a/user_mgr.cpp b/user_mgr.cpp
index 5bb1509..4f49925 100644
--- a/user_mgr.cpp
+++ b/user_mgr.cpp
@@ -61,15 +61,14 @@
 static constexpr int failure = -1;
 
 // pam modules related
-static constexpr const char* pamPWHistory = "pam_pwhistory.so";
 static constexpr const char* minPasswdLenProp = "minlen";
 static constexpr const char* remOldPasswdCount = "remember";
 static constexpr const char* maxFailedAttempt = "deny";
 static constexpr const char* unlockTimeout = "unlock_time";
-static constexpr const char* defaultPamPasswdConfigFile =
-    "/etc/pam.d/common-password";
 static constexpr const char* defaultFaillockConfigFile =
     "/etc/security/faillock.conf";
+static constexpr const char* defaultPWHistoryConfigFile =
+    "/etc/security/pwhistory.conf";
 static constexpr const char* defaultPWQualityConfigFile =
     "/etc/security/pwquality.conf";
 
@@ -596,8 +595,8 @@
     {
         return value;
     }
-    if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
-                             std::to_string(value)) != success)
+    if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
+                              std::to_string(value)) != success)
     {
         lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
                    value);
@@ -638,51 +637,6 @@
     return AccountPolicyIface::accountUnlockTimeout(value);
 }
 
-int UserMgr::getPamModuleArgValue(const std::string& moduleName,
-                                  const std::string& argName,
-                                  std::string& argValue)
-{
-    std::string fileName = pamPasswdConfigFile;
-    std::ifstream fileToRead(fileName, std::ios::in);
-    if (!fileToRead.is_open())
-    {
-        lg2::error("Failed to open pam configuration file {FILENAME}",
-                   "FILENAME", fileName);
-        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 (line.find(moduleName) != std::string::npos)
-        {
-            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::getPamModuleConfValue(const std::string& confFile,
                                    const std::string& argName,
                                    std::string& argValue)
@@ -724,72 +678,6 @@
     return failure;
 }
 
-int UserMgr::setPamModuleArgValue(const std::string& moduleName,
-                                  const std::string& argName,
-                                  const std::string& argValue)
-{
-    std::string fileName = pamPasswdConfigFile;
-    std::string tmpFileName = fileName + "_tmp";
-    std::ifstream fileToRead(fileName, std::ios::in);
-    std::ofstream fileToWrite(tmpFileName, std::ios::out);
-    if (!fileToRead.is_open() || !fileToWrite.is_open())
-    {
-        lg2::error("Failed to open pam configuration file {FILENAME}",
-                   "FILENAME", fileName);
-        // Delete the unused tmp file
-        std::remove(tmpFileName.c_str());
-        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 (line.find(moduleName) != std::string::npos)
-        {
-            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(tmpFileName.c_str(), fileName.c_str()) == 0)
-        {
-            return success;
-        }
-    }
-    // No changes, so delete the unused tmp file
-    std::remove(tmpFileName.c_str());
-    return failure;
-}
-
 int UserMgr::setPamModuleConfValue(const std::string& confFile,
                                    const std::string& argName,
                                    const std::string& argValue)
@@ -1443,8 +1331,8 @@
         AccountPolicyIface::minPasswordLength(value);
     }
     valueStr.clear();
-    if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
-        success)
+    if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
+                              valueStr) != success)
     {
         AccountPolicyIface::rememberOldPasswordTimes(0);
     }
@@ -1589,8 +1477,8 @@
 
 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
-    pamPasswdConfigFile(defaultPamPasswdConfigFile),
     faillockConfigFile(defaultFaillockConfigFile),
+    pwHistoryConfigFile(defaultPWHistoryConfigFile),
     pwQualityConfigFile(defaultPWQualityConfigFile)
 {
     UserMgrIface::allPrivileges(privMgr);
diff --git a/user_mgr.hpp b/user_mgr.hpp
index f182968..d7b731c 100644
--- a/user_mgr.hpp
+++ b/user_mgr.hpp
@@ -494,8 +494,8 @@
 
     friend class TestUserMgr;
 
-    std::string pamPasswdConfigFile;
     std::string faillockConfigFile;
+    std::string pwHistoryConfigFile;
     std::string pwQualityConfigFile;
 };