blob: 447bfcd30be381a0566a7f5bc1add1c24bea16df [file] [log] [blame]
/**
* 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 <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <shadow.h>
#include <array>
#include <random>
#include <errno.h>
#include <xyz/openbmc_project/Common/error.hpp>
#include <phosphor-logging/log.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/elog-errors.hpp>
#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<char,1024> 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<char> tempFileName(shadowFile.begin(), shadowFile.end());
std::vector<char> 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<char*>(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<level::ERR>(message.c_str());
elog<InsufficientPermission>();
}
else
{
log<level::ERR>(errMsg.c_str(),
entry("USER=%s",user.c_str()),
entry("ERRNO=%d", errNo));
elog<InternalFailure>();
}
}
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<level::ERR>("Error finding crypt algo",
entry("USER=%s",user.c_str()));
elog<InternalFailure>();
}
// 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