/** * Copyright © 2017 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #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; using namespace phosphor::logging; using InsufficientPermission = sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; // 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); // 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, newPassword, salt); return; } void User::applyPassword(const std::string& shadowFile, const std::string& password, const std::string& salt) { // Needed by getspnam_r struct spwd shdp; struct spwd* pshdp; // This should be fine even if SHA512 is used. std::array buffer{}; // Open the shadow file for reading phosphor::user::File shadow(shadowFile, "r"); if ((shadow)() == NULL) { return raiseException(errno, "Error opening shadow file"); } // open temp shadow file, by suffixing random name in shadow file name. std::vector tempFileName(shadowFile.begin(), shadowFile.end()); std::vector fileTemplate = {'_', '_', 'X', 'X', 'X', 'X', 'X', 'X', '\0'}; tempFileName.insert(tempFileName.end(), fileTemplate.begin(), fileTemplate.end()); int fd = mkstemp(tempFileName.data()); if (fd == -1) { return raiseException(errno, "Error creating temp shadow file"); } std::string strTempFileName(tempFileName.data()); // Open the temp shadow file for writing from provided fd // By "true", remove it at exit if still there. // This is needed to cleanup the temp file at exception phosphor::user::File temp(fd, strTempFileName, "w", true); if ((temp)() == NULL) { close(fd); return raiseException(errno, "Error opening temp shadow file"); } fd = -1; // don't use fd anymore, as the File object owns it // 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 raiseException(errno, "Error reading shadow file mode"); } r = fchmod(fileno((temp)()), st.st_mode); if (r < 0) { return raiseException(errno, "Error setting temp file mode"); } // Read shadow file and process while (true) { auto r = fgetspent_r((shadow)(), &shdp, buffer.data(), buffer.max_size(), &pshdp); if (r) { if (errno == EACCES || errno == ERANGE) { return raiseException(errno, "Error reading shadow file"); } else { // Seem to have run over all 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(hash.c_str()); } // Apply r = putspent(&shdp, (temp)()); if (r < 0) { return raiseException(errno, "Error updating temp shadow file"); } } // All entries // Done endspent(); // flush contents to file first, before renaming to avoid // corruption during power failure fflush((temp)()); // Everything must be fine at this point fs::rename(strTempFileName, shadowFile); return; } void User::raiseException(int errNo, const std::string& errMsg) { using namespace std::string_literals; if (errNo == EACCES) { auto message = "Access denied "s + errMsg; log(message.c_str()); elog(); } else { log(errMsg.c_str(), entry("USER=%s", user.c_str()), entry("ERRNO=%d", errNo)); elog(); } } 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()) { log("Error finding crypt algo", entry("USER=%s", user.c_str())); elog(); } // 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); } // Returns specific format of salt string std::string User::getSaltString(const std::string& crypt, const std::string& salt) { return '$' + crypt + '$' + salt + '$'; } // Given a password and salt, generates hash std::string User::generateHash(const std::string& password, const std::string& salt) { return crypt(password.c_str(), salt.c_str()); } } // namespace user } // namespace phosphor