Update shadow password file with new password

Change-Id: Ida7c1aba6f17ac6f006f159d08e2638808f3a54c
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
diff --git a/user.cpp b/user.cpp
index f4b0e5e..24cd988 100644
--- a/user.cpp
+++ b/user.cpp
@@ -16,17 +16,58 @@
 #include <cstring>
 #include <unistd.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <shadow.h>
 #include <array>
+#include <random>
+#include <errno.h>
 #include "user.hpp"
+#include "file.hpp"
+#include "shadowlock.hpp"
+#include "config.h"
 namespace phosphor
 {
 namespace user
 {
 
+constexpr auto SHADOW_FILE = "/etc/shadow";
+
+// See crypt(3)
+constexpr int SALT_LENGTH = 16;
+
 // Sets or updates the password
 void User::setPassword(std::string newPassword)
 {
+    // Gate any access to /etc/shadow
+    phosphor::user::shadow::Lock lock();
+
+    // rewind to the start of shadow entry
+    setspent();
+
+    // Generate a random string from set [A-Za-z0-9./]
+    std::string salt{};
+    salt.resize(SALT_LENGTH);
+    salt = randomString(SALT_LENGTH);
+
+    auto tempShadowFile = std::string("/etc/__") +
+                          randomString(SALT_LENGTH) +
+                          std::string("__");
+
+    // Apply the change. Updates could be directly made to shadow
+    // but then any update must be contained within the boundary
+    // of that user, else it would run into next entry and thus
+    // corrupting it. Classic example is when a password is set on
+    // a user ID that does not have a prior password
+    applyPassword(SHADOW_FILE, tempShadowFile,
+                  newPassword, salt);
+    return;
+}
+
+void User::applyPassword(const std::string& shadowFile,
+                         const std::string& tempFile,
+                         const std::string& password,
+                         const std::string& salt)
+{
     // Needed by getspnam_r
     struct spwd shdp;
     struct spwd* pshdp;
@@ -34,33 +75,132 @@
     // This should be fine even if SHA512 is used.
     std::array<char,1024> buffer{};
 
-    // 1: Read /etc/shadow for the user
-    auto r = getspnam_r(user.c_str(), &shdp, buffer.data(),
-                        buffer.max_size(), &pshdp);
+    // Open the shadow file for reading
+    phosphor::user::File shadow(shadowFile, "r");
+    if ((shadow)() == NULL)
+    {
+        throw std::runtime_error("Error opening shadow file");
+        // TODO: Throw error
+    }
+
+    // Open the temp shadow file for writing
+    // By "true", remove it at exit if still there.
+    // This is needed to cleanup the temp file at exception
+    phosphor::user::File temp(tempFile, "w", true);
+    if ((temp)() == NULL)
+    {
+        throw std::runtime_error("Error opening temp shadow file");
+        // TODO: Throw error
+    }
+
+    // Change the permission of this new temp file
+    // to be same as shadow so that it's secure
+    struct stat st{};
+    auto r = fstat(fileno((shadow)()), &st);
     if (r < 0)
     {
-        return;
-        // TODO: Throw an error
+        throw std::runtime_error("Error reading permission of shadow");
+        // TODO: Throw error
     }
 
-    // Done reading
+    r = fchmod(fileno((temp)()), st.st_mode);
+    if (r < 0)
+    {
+        throw std::runtime_error("Error setting permission on temp file");
+        // TODO: Throw error
+    }
+
+    // Read shadow file and process
+    while (true)
+    {
+        auto r = fgetspent_r((shadow)(), &shdp, buffer.data(),
+                             buffer.max_size(), &pshdp);
+        if (r)
+        {
+            // Done with all entries
+            break;
+        }
+
+        // Hash of password if the user matches
+        std::string hash{};
+
+        // Matched user
+        if (user == shdp.sp_namp)
+        {
+            // Update with new hashed password
+            hash = hashPassword(shdp.sp_pwdp, password, salt);
+            shdp.sp_pwdp = const_cast<char*>(hash.c_str());
+        }
+
+        // Apply
+        r = putspent(&shdp, (temp)());
+        if (r < 0)
+        {
+            throw std::runtime_error("Error updating temp shadow entry");
+            // TODO: Throw exception
+        }
+    } // All entries
+
+    // Done
     endspent();
 
-    // 2: Parse and get crypt algo
-    auto cryptAlgo = getCryptField(shdp.sp_pwdp);
+    // Everything must be fine at this point
+    fs::rename(tempFile, shadowFile);
+    return;
+}
+
+std::string User::hashPassword(char* spPwdp,
+                               const std::string& password,
+                               const std::string& salt)
+{
+    // Parse and get crypt algo
+    auto cryptAlgo = getCryptField(spPwdp);
     if (cryptAlgo.empty())
     {
-        // TODO: Throw error getting crypt field
+        throw std::runtime_error("Error finding crypt algo");
+        // TODO: Throw error
     }
 
-    // TODO: Update the password in next commit
-    return;
+    // Update shadow password pointer with hash
+    auto saltString = getSaltString(cryptAlgo, salt);
+    return generateHash(password, saltString);
+}
+
+// Returns a random string in set [A-Za-z0-9./]
+// of size numChars
+const std::string User::randomString(int length)
+{
+    // Populated random string
+    std::string random{};
+
+    // Needed per crypt(3)
+    std::string set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk"
+                      "lmnopqrstuvwxyz0123456789./";
+
+    // Will be used to obtain a seed for the random number engine
+    std::random_device rd;
+
+    // Standard mersenne_twister_engine seeded with rd()
+    std::mt19937 gen(rd());
+
+    std::uniform_int_distribution<> dis(0, set.size()-1);
+    for (int count = 0; count < length; count++)
+    {
+        // Use dis to transform the random unsigned int generated by
+        // gen into a int in [1, SALT_LENGTH]
+        random.push_back(set.at(dis(gen)));
+    }
+    return random;
 }
 
 // Extract crypto algorithm field
 CryptAlgo User::getCryptField(char* spPwdp)
 {
     char* savePtr{};
+    if (std::string{spPwdp}.front() != '$')
+    {
+        return DEFAULT_CRYPT_ALGO;
+    }
     return strtok_r(spPwdp, "$", &savePtr);
 }