summaryrefslogtreecommitdiffstats
path: root/user.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'user.cpp')
-rw-r--r--user.cpp162
1 files changed, 151 insertions, 11 deletions
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 @@ void User::setPassword(std::string newPassword)
// 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);
}
OpenPOWER on IntegriCloud