diff --git a/greeter/authenticator.h b/greeter/authenticator.h --- a/greeter/authenticator.h +++ b/greeter/authenticator.h @@ -24,13 +24,19 @@ class QSocketNotifier; class QTimer; +class KCheckPass; + +enum class AuthenticationMode { + Delayed, + Direct +}; class Authenticator : public QObject { Q_OBJECT Q_PROPERTY(bool graceLocked READ isGraceLocked NOTIFY graceLockedChanged) public: - explicit Authenticator(QObject *parent = nullptr); + explicit Authenticator(AuthenticationMode mode = AuthenticationMode::Direct, QObject *parent = nullptr); ~Authenticator(); bool isGraceLocked() const; @@ -46,18 +52,30 @@ void error(const QString & err); // don't remove the "err" param, used in QML!!! private: + void setupCheckPass(); QTimer *m_graceLockTimer; + KCheckPass *m_checkPass; }; class KCheckPass : public QObject { Q_OBJECT public: - explicit KCheckPass(const QString &password, QObject *parent = nullptr); + explicit KCheckPass(AuthenticationMode mode, QObject *parent = nullptr); ~KCheckPass(); void start(); + bool isReady() const { + return m_ready; + } + + void setPassword(const QString &password) { + m_password = password; + } + + void startAuth(); + Q_SIGNALS: void failed(); void succeeded(); @@ -84,6 +102,8 @@ QSocketNotifier *m_notifier; int m_pid; int m_fd; + bool m_ready = false; + AuthenticationMode m_mode; }; #endif diff --git a/greeter/authenticator.cpp b/greeter/authenticator.cpp --- a/greeter/authenticator.cpp +++ b/greeter/authenticator.cpp @@ -25,6 +25,7 @@ #include // Qt +#include #include #include #include @@ -36,13 +37,19 @@ #include #include -Authenticator::Authenticator(QObject *parent) +Authenticator::Authenticator(AuthenticationMode mode, QObject *parent) : QObject(parent) , m_graceLockTimer(new QTimer(this)) + , m_checkPass(nullptr) { m_graceLockTimer->setSingleShot(true); m_graceLockTimer->setInterval(3000); connect(m_graceLockTimer, &QTimer::timeout, this, &Authenticator::graceLockedChanged); + + if (mode == AuthenticationMode::Delayed) { + m_checkPass = new KCheckPass(AuthenticationMode::Delayed, this); + setupCheckPass(); + } } Authenticator::~Authenticator() = default; @@ -56,31 +63,56 @@ m_graceLockTimer->start(); emit graceLockedChanged(); - KCheckPass *checkPass = new KCheckPass(password, this); - connect(checkPass, &KCheckPass::succeeded, this, &Authenticator::succeeded); - connect(checkPass, &KCheckPass::failed, this, &Authenticator::failed); - connect(checkPass, &KCheckPass::message, this, &Authenticator::message); - connect(checkPass, &KCheckPass::error, this, &Authenticator::error); - checkPass->start(); + if (!m_checkPass) { + m_checkPass = new KCheckPass(AuthenticationMode::Direct, this); + m_checkPass->setPassword(password); + setupCheckPass(); + } else { + if (!m_checkPass->isReady()) { + emit failed(); + return; + } + m_checkPass->setPassword(password); + m_checkPass->startAuth(); + } +} + +void Authenticator::setupCheckPass() +{ + connect(m_checkPass, &KCheckPass::succeeded, this, &Authenticator::succeeded); + connect(m_checkPass, &KCheckPass::failed, this, &Authenticator::failed); + connect(m_checkPass, &KCheckPass::message, this, &Authenticator::message); + connect(m_checkPass, &KCheckPass::error, this, &Authenticator::error); + connect(m_checkPass, &KCheckPass::destroyed, this, + [this] { + m_checkPass = nullptr; + } + ); + m_checkPass->start(); } bool Authenticator::isGraceLocked() const { return m_graceLockTimer->isActive(); } -KCheckPass::KCheckPass(const QString &password, QObject *parent) +KCheckPass::KCheckPass(AuthenticationMode mode, QObject *parent) : QObject(parent) - , m_password(password) , m_notifier(nullptr) , m_pid(0) , m_fd(0) + , m_mode(mode) { - connect(this, &KCheckPass::succeeded, this, &QObject::deleteLater); - connect(this, &KCheckPass::failed, this, &QObject::deleteLater); + if (mode == AuthenticationMode::Direct) { + connect(this, &KCheckPass::succeeded, this, &QObject::deleteLater); + connect(this, &KCheckPass::failed, this, &QObject::deleteLater); + } } -KCheckPass::~KCheckPass() = default; +KCheckPass::~KCheckPass() +{ + reapVerify(); +} void KCheckPass::start() { @@ -194,6 +226,7 @@ void KCheckPass::handleVerify() { + m_ready = false; int ret; char *arr; @@ -253,9 +286,21 @@ case ConvPutAuthAbort: // what to do here? return; + case ConvPutReadyForAuthentication: + m_ready = true; + if (m_mode == AuthenticationMode::Direct) { + ::kill(m_pid, SIGUSR1); + } + return; } } - reapVerify(); + if (m_mode == AuthenticationMode::Direct) { + reapVerify(); + } else { + // we broke, let's restart the greeter + // error code 1 will result in a restart through the system + qApp->exit(1); + } } void KCheckPass::reapVerify() @@ -265,6 +310,7 @@ m_notifier = nullptr; ::close( m_fd ); int status; + ::kill(m_pid, SIGUSR2); while (::waitpid( m_pid, &status, 0 ) < 0) if (errno != EINTR) { // This should not happen ... cantCheck(); @@ -277,3 +323,8 @@ // TODO: better signal? emit failed(); } + +void KCheckPass::startAuth() +{ + ::kill(m_pid, SIGUSR1); +} diff --git a/greeter/autotests/authenticatortest.cpp b/greeter/autotests/authenticatortest.cpp --- a/greeter/autotests/authenticatortest.cpp +++ b/greeter/autotests/authenticatortest.cpp @@ -33,7 +33,7 @@ void AuthenticatorTest::testIncorrectPassword() { - QScopedPointer authenticator(new Authenticator(this)); + QScopedPointer authenticator(new Authenticator()); QSignalSpy failedSpy(authenticator.data(), SIGNAL(failed())); QSignalSpy succeededSpy(authenticator.data(), SIGNAL(succeeded())); QSignalSpy graceLockSpy(authenticator.data(), SIGNAL(graceLockedChanged())); @@ -56,7 +56,7 @@ void AuthenticatorTest::testCorrectPassword() { - QScopedPointer authenticator(new Authenticator(this)); + QScopedPointer authenticator(new Authenticator()); QSignalSpy succeededSpy(authenticator.data(), SIGNAL(succeeded())); QSignalSpy failedSpy(authenticator.data(), SIGNAL(failed())); authenticator->tryUnlock(QStringLiteral("testpassword")); @@ -67,7 +67,7 @@ void AuthenticatorTest::testMessage() { - QScopedPointer authenticator(new Authenticator(this)); + QScopedPointer authenticator(new Authenticator()); QSignalSpy succeededSpy(authenticator.data(), SIGNAL(succeeded())); QSignalSpy messageSpy(authenticator.data(), SIGNAL(message(QString))); authenticator->tryUnlock(QStringLiteral("info")); @@ -80,7 +80,7 @@ void AuthenticatorTest::testError() { - QScopedPointer authenticator(new Authenticator(this)); + QScopedPointer authenticator(new Authenticator()); QSignalSpy failedSpy(authenticator.data(), SIGNAL(failed())); QSignalSpy errorSpy(authenticator.data(), SIGNAL(error(QString))); authenticator->tryUnlock(QStringLiteral("error")); diff --git a/greeter/greeterapp.cpp b/greeter/greeterapp.cpp --- a/greeter/greeterapp.cpp +++ b/greeter/greeterapp.cpp @@ -98,7 +98,7 @@ , m_testing(false) , m_ignoreRequests(false) , m_immediateLock(false) - , m_authenticator(new Authenticator(this)) + , m_authenticator(new Authenticator(AuthenticationMode::Direct, this)) , m_graceTime(0) , m_noLock(false) , m_defaultToSwitchUser(false) diff --git a/kcheckpass/kcheckpass-enums.h b/kcheckpass/kcheckpass-enums.h --- a/kcheckpass/kcheckpass-enums.h +++ b/kcheckpass/kcheckpass-enums.h @@ -58,7 +58,8 @@ ConvPutAuthSucceeded, ConvPutAuthFailed, ConvPutAuthError, - ConvPutAuthAbort + ConvPutAuthAbort, + ConvPutReadyForAuthentication } ConvRequest; /* these must match the defs in kgreeterplugin.h */ diff --git a/kcheckpass/kcheckpass.c b/kcheckpass/kcheckpass.c --- a/kcheckpass/kcheckpass.c +++ b/kcheckpass/kcheckpass.c @@ -56,6 +56,8 @@ #include #include #include +#include +#include #include #if HAVE_SYS_PRCTL_H @@ -210,6 +212,7 @@ case ConvPutAuthFailed: case ConvPutAuthError: case ConvPutAuthAbort: + case ConvPutReadyForAuthentication: return 0; case ConvPutInfo: case ConvPutError: @@ -261,6 +264,13 @@ int c, nfd; uid_t uid; AuthReturn ret; + sigset_t signalMask; + int signalFd; + struct signalfd_siginfo fdsi; + ssize_t sigReadSize; + pid_t parentPid; + + parentPid = getppid(); // disable ptrace on kcheckpass #if HAVE_PR_SET_DUMPABLE @@ -321,32 +331,75 @@ return AuthError; } - /* Now do the fandango */ - ret = Authenticate(method, - username, - conv_server); - - if (ret == AuthBad) { - message("Authentication failure\n"); - if (!nullpass) { - openlog("kcheckpass", LOG_PID, LOG_AUTH); - syslog(LOG_NOTICE, "Authentication failure for %s (invoked by uid %d)", username, uid); - } + // setup signals + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGUSR1); + sigaddset(&signalMask, SIGUSR2); + // block them + if (sigprocmask(SIG_BLOCK, &signalMask, NULL) == -1) { + message("Block signal failed\n"); + conv_server(ConvPutAuthError, 0); + return 1; } - switch (ret) { - case AuthOk: - conv_server(ConvPutAuthSucceeded, 0); - break; - case AuthBad: - conv_server(ConvPutAuthFailed, 0); - break; - case AuthError: - conv_server(ConvPutAuthError, 0); - break; - case AuthAbort: - conv_server(ConvPutAuthAbort, 0); - default: + signalFd = signalfd(-1, &signalMask, SFD_CLOEXEC); + if (signalFd == -1) { + message("Signal fd failed\n"); + conv_server(ConvPutAuthError, 0); + return 1; + } + // now lets block on the fd + for (;;) { + conv_server(ConvPutReadyForAuthentication, 0); + sigReadSize = read(signalFd, &fdsi, sizeof(struct signalfd_siginfo)); + if (sigReadSize != sizeof(struct signalfd_siginfo)) { + message("Read wrong size\n"); + return 1; + } + if (fdsi.ssi_signo == SIGUSR1) { + if (fdsi.ssi_pid != parentPid) { + message("signal from wrong process\n"); + continue; + } + /* Now do the fandango */ + ret = Authenticate(method, + username, + conv_server); + + if (ret == AuthBad) { + message("Authentication failure\n"); + if (!nullpass) { + openlog("kcheckpass", LOG_PID, LOG_AUTH); + syslog(LOG_NOTICE, "Authentication failure for %s (invoked by uid %d)", username, uid); + } + } + switch (ret) { + case AuthOk: + conv_server(ConvPutAuthSucceeded, 0); + break; + case AuthBad: + conv_server(ConvPutAuthFailed, 0); + break; + case AuthError: + conv_server(ConvPutAuthError, 0); + break; + case AuthAbort: + conv_server(ConvPutAuthAbort, 0); + default: + break; + } + if (uid != geteuid()) { + // we don't support multiple auth for setuid kcheckpass + break; + } + } else if (fdsi.ssi_signo == SIGUSR2) { + if (fdsi.ssi_pid != parentPid) { + message("signal from wrong process\n"); + continue; + } break; + } else { + message("unexpected signal\n"); + } } return 0; diff --git a/tests/kcheckpass_test.cpp b/tests/kcheckpass_test.cpp --- a/tests/kcheckpass_test.cpp +++ b/tests/kcheckpass_test.cpp @@ -19,13 +19,30 @@ *********************************************************************/ #include "../greeter/authenticator.h" #include +#include #include #include int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); - Authenticator authenticator; + QCommandLineParser parser; + QCommandLineOption delayedOption(QStringLiteral("delayed"), + QStringLiteral("KCheckpass is created at startup, the authentication is delayed")); + QCommandLineOption directOption(QStringLiteral("direct"), + QStringLiteral("A new KCheckpass gets created when trying to authenticate")); + parser.addOption(directOption); + parser.addOption(delayedOption); + parser.addHelpOption(); + parser.process(app); + AuthenticationMode mode = AuthenticationMode::Delayed; + if (parser.isSet(directOption)) { + mode = AuthenticationMode::Direct; + } + if (parser.isSet(directOption) && parser.isSet(delayedOption)) { + parser.showHelp(0); + } + Authenticator authenticator(mode); QQuickView view; view.rootContext()->setContextProperty("authenticator", &authenticator);