/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see .
*/
#include "PAMPasswordService.hpp"
#ifndef LMS_SUPPORT_PAM
#error "Should not compile this"
#endif
#include
#include
#include "services/auth/Types.hpp"
#include "services/database/Session.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
namespace
{
class PAMError
{
public:
PAMError(std::string_view msg, pam_handle_t* pamh, int err)
{
_errorMsg = std::string{ msg } + ": " + pam_strerror(pamh, err);
}
std::string_view message() const { return _errorMsg; }
private:
std::string _errorMsg;
};
class PAMContext
{
public:
PAMContext(std::string_view loginName)
{
int err{ pam_start("lms", std::string {loginName}.c_str(), &_conv, &_pamh) };
if (err != PAM_SUCCESS)
throw PAMError{ "start failed", _pamh, err };
}
~PAMContext()
{
int err{ pam_end(_pamh, 0) };
if (err != PAM_SUCCESS)
LMS_LOG(AUTH, ERROR, "end failed: " << pam_strerror(_pamh, err));
}
void authenticate(std::string_view password)
{
AuthenticateConvContext authContext{ password };
ScopedConvContextSetter scopedContext{ *this, authContext };
int err{ pam_authenticate(_pamh, 0) };
if (err != PAM_SUCCESS)
throw PAMError{ "authenticate failed", _pamh, err };
}
void validateAccount()
{
int err{ pam_acct_mgmt(_pamh, PAM_SILENT) };
if (err != PAM_SUCCESS)
throw PAMError{ "acct_mgmt failed", _pamh, err };
}
private:
class ConvContext
{
public:
virtual ~ConvContext() = default;
};
class AuthenticateConvContext final : public ConvContext
{
public:
AuthenticateConvContext(std::string_view password) : _password{ password } {}
std::string_view getPassword() const { return _password; }
private:
std::string_view _password;
};
class ScopedConvContextSetter
{
public:
ScopedConvContextSetter(PAMContext& pamContext, ConvContext& convContext)
: _pamContext{ pamContext }
{
_pamContext._convContext = &convContext;
}
~ScopedConvContextSetter()
{
_pamContext._convContext = nullptr;
}
ScopedConvContextSetter(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter(ScopedConvContextSetter&&) = delete;
ScopedConvContextSetter& operator=(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter& operator=(ScopedConvContextSetter&&) = delete;
private:
PAMContext& _pamContext;
};
static int conv(int msgCount, const pam_message** msgs, pam_response** resps, void* userData)
{
if (msgCount < 1)
return PAM_CONV_ERR;
if (!resps || !msgs || !userData)
return PAM_CONV_ERR;
PAMContext& context{ *static_cast(userData) };
AuthenticateConvContext* authenticateContext = dynamic_cast(context._convContext);
if (!authenticateContext)
{
LMS_LOG(AUTH, ERROR, "Unexpected conv!");
return PAM_CONV_ERR;
}
// Only expect a PAM_PROMPT_ECHO_OFF msg
if (msgCount != 1 || msgs[0]->msg_style != PAM_PROMPT_ECHO_OFF)
{
LMS_LOG(AUTH, ERROR, "Unexpected conv message. Count = " << msgCount);
return PAM_CONV_ERR;
}
pam_response* response{ static_cast(malloc(sizeof(pam_response))) };
if (!response)
return PAM_CONV_ERR;
response->resp = strdup(std::string{ authenticateContext->getPassword() }.c_str());
*resps = response;
return PAM_SUCCESS;
}
ConvContext* _convContext{};
pam_conv _conv{ &PAMContext::conv, this };
pam_handle_t* _pamh{};
};
}
bool PAMPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
try
{
LMS_LOG(AUTH, DEBUG, "Checking PAM password for user '" << loginName << "'");
PAMContext pamContext{ loginName };
pamContext.authenticate(password);
pamContext.validateAccount();
return true;
}
catch (const PAMError& error)
{
LMS_LOG(AUTH, ERROR, "PAM error: " << error.message());
return false;
}
}
bool PAMPasswordService::canSetPasswords() const
{
return false;
}
IPasswordService::PasswordAcceptabilityResult PAMPasswordService::checkPasswordAcceptability(std::string_view, const PasswordValidationContext&) const
{
throw NotImplementedException{};
}
void PAMPasswordService::setPassword(Database::UserId, std::string_view)
{
throw NotImplementedException{};
}
} // namespace Auth