rublon-ssh/PAM/ssh/lib/pam.cpp
rublon-bwi 6a3882fa47
Bwi/v2.0.2 rc1 (#10)
* Remove unused options from rublon default config

* Remove safe|secure options

* Allow 9 digits long passcode for passcode bypass

* Change name of 'Mobile Passcode' to 'Passcode'

* Do not display any prompt when user is waiting

* remove unused alloca.h header

* Add autopushPrompt option

* Change name OTP method

* Change enrolement message handling

* ad static string ctor

* Addded postrm script

* Rename 01_rublon_ssh.conf to 01-rublon-ssh.conf

* restart sshd service after rublon package instalation

* Fix sshd not restarting bug on ubuntu 24.04

* disable logging from websocket-io

* change package name to match old package name

* Fix compilation issue when using non owning ptr

* Set version to 2.0.0
2024-06-17 08:57:26 +02:00

185 lines
8.2 KiB
C++
Executable File

#include <security/pam_appl.h>
#include <security/pam_client.h>
#include <security/pam_ext.h>
#include <security/pam_misc.h>
#include <security/pam_modules.h>
#include <syslog.h>
#include <rublon/check_application.hpp>
#include <rublon/error.hpp>
#include <rublon/finish.hpp>
#include <rublon/rublon.hpp>
#include <rublon/utils.hpp>
#define DLL_PUBLIC __attribute__((visibility("default")))
DLL_PUBLIC int pam_sm_setcred([[maybe_unused]] pam_handle_t * pamh,
[[maybe_unused]] int flags,
[[maybe_unused]] int argc,
[[maybe_unused]] const char ** argv) {
return PAM_SUCCESS;
}
DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh,
[[maybe_unused]] int flags,
[[maybe_unused]] int argc,
[[maybe_unused]] const char ** argv) {
return PAM_SUCCESS;
}
std::string g_tid;
DLL_PUBLIC int
pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) {
using namespace rublon;
details::initLog();
LinuxPam pam{pamh};
auto printAuthMessageAndExit = [&](const AuthenticationStatus status) {
switch(status.action()) {
case AuthenticationStatus::Action::Bypass:
pam.print("RUBLON authentication BYPASSED");
return PAM_SUCCESS;
case AuthenticationStatus::Action::Denied:
pam.print("RUBLON authentication FAILED");
return PAM_MAXTRIES;
case AuthenticationStatus::Action::Confirmed:
pam.print("RUBLON authentication SUCCEEDED");
return PAM_SUCCESS;
}
pam.print("RUBLON connector has exited with unknown code, access DENY!\n");
return PAM_MAXTRIES;
};
auto session = rublon::RublonFactory{}.startSession(pam);
if(not session.has_value()) {
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
if(!session->config().logging){
g_level = LogLevel::Warning;
}
auto & CH = session.value().coreHandler();
auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); };
auto confirmMethod = [&](const PostMethod & postMethod) { return postMethod.handle(CH); };
auto confirmCode = [&](const MethodProxy & method) mutable { return method.fire(CH, pam); };
auto finalizeTransaction = [&](const AuthenticationStatus & status) -> tl::expected< AuthenticationStatus, Error > {
if(status.userAuthorized()) {
auto tok = std::string{status.accessToken().substr(10, 60).data(), 60};
Finish finish{session.value().config(), std::move(tok)};
finish.handle(CH);
}
return status;
};
auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { return printAuthMessageAndExit(status); };
auto mapError = [&](const Error & error) -> tl::expected< int, Error > {
log(LogLevel::Error, "Process interrupted by {%s::%s}", error.errorClassName(), error.categoryName());
if(error.is< RublonAuthenticationInterrupt >()) {
switch(error.get< RublonAuthenticationInterrupt >().errorClass) {
case RublonAuthenticationInterrupt::ErrorClass::UserBaypass:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
case RublonAuthenticationInterrupt::ErrorClass::UserDenied:
pam.print("Access denied! Contact your administrator for more information");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case RublonAuthenticationInterrupt::ErrorClass::UserWaiting:
case RublonAuthenticationInterrupt::ErrorClass::UserPending:
pam.print(
"Your account is awaiting administrator's approval.\n"
"Contact your administrator and ask them to approve your account");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case RublonAuthenticationInterrupt::ErrorClass::UserNotFound:
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
}
if(error.is< MethodError >()) {
switch(error.get< MethodError >().errorClass) {
case MethodError::ErrorClass::BadMethod:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case MethodError::ErrorClass::BadUserInput:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case MethodError::ErrorClass::NoMethodAvailable:
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}
if(error.is< ConnectionError >()) {
if(session.value().config().failMode == FailMode::deny) {
pam.print("Incorrect response from the Rublon API, user bypassed");
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
} else {
pam.print("Incorrect response from the Rublon API, user access denied");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}
if(error.is< CoreHandlerError >()) {
const auto & reson = error.get< CoreHandlerError >().reson;
pam.print("Something went wrong and authentication could not be completed, contact your administrator");
if(reson == "UserBypassedException" or reson == "UserNotFoundException")
return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass);
}
if(error.is< WerificationError >()) {
switch(error.get< WerificationError >().errorClass) {
case WerificationError::ErrorClass::PasscodeException:
pam.print(R"(Incorrect passcode)");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case WerificationError::ErrorClass::BadInput:
pam.print(R"(Ensure that the Secret Key is correct.)");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}
if(error.is< RublonCheckApplicationException >()) {
switch(error.get< RublonCheckApplicationException >().errorClass) {
case RublonCheckApplicationException::ErrorClass::ApplicationNotFoundException:
log(LogLevel::Error, R"(Could not find the application in the Rublon Admin Console.)");
log(LogLevel::Error, R"(Ensure that the application exists and the SystemToken is correct.)");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case RublonCheckApplicationException::ErrorClass::InvalidSignatureException:
log(LogLevel::Error, R"(Could not verify the signature.)");
log(LogLevel::Error, R"(Ensure that the Secret Key is correct.)");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
case RublonCheckApplicationException::ErrorClass::UnsupportedVersionException:
log(LogLevel::Error, R"(The provided version of the app is unsupported.)");
log(LogLevel::Error, R"(Try changing the app version.)");
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
}
}
return printAuthMessageAndExit(AuthenticationStatus::Action::Denied);
};
{
CheckApplication ca;
auto ret =
ca.call(CH, {session.value().config().systemToken.data(), session.value().config().systemToken.size()}).or_else(mapError);
if(not ret.has_value()) {
log(LogLevel::Error, "Check Application step failed, check configration");
return PAM_MAXTRIES;
}
}
auto ret = Init{session.value()}
.handle(CH, pam) //
.and_then(selectMethod)
.and_then(confirmMethod)
.and_then(confirmCode)
.and_then(finalizeTransaction)
.and_then(allowLogin)
.or_else(mapError);
return ret.value_or(PAM_MAXTRIES);
}