diff options
Diffstat (limited to 'user.cpp')
-rw-r--r-- | user.cpp | 162 |
1 files changed, 151 insertions, 11 deletions
@@ -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); } |