File indexing completed on 2024-04-28 05:27:42

0001 /*
0002     SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "pamauthenticator.h"
0008 
0009 #include <QDebug>
0010 #include <QEventLoop>
0011 #include <QMetaMethod>
0012 #include <security/pam_appl.h>
0013 
0014 #include "kscreenlocker_greet_logging.h"
0015 
0016 class PamWorker : public QObject
0017 {
0018     Q_OBJECT
0019 public:
0020     PamWorker();
0021     ~PamWorker() override;
0022     Q_DISABLE_COPY_MOVE(PamWorker)
0023     void start(const QString &service, const QString &user);
0024     void authenticate();
0025 
0026 Q_SIGNALS:
0027     void busyChanged(bool busy);
0028     void promptForSecret(const QString &msg);
0029     void prompt(const QString &msg);
0030     void infoMessage(const QString &msg);
0031     void errorMessage(const QString &msg);
0032     void failed();
0033     void succeeded();
0034     void unavailabilityChanged(bool unavailable);
0035     void inAuthenticateChanged(bool inAuthenticate);
0036 
0037     // internal
0038     void promptResponseReceived(const QByteArray &prompt);
0039     void cancelled();
0040 
0041 private:
0042     static int converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data);
0043 
0044     pam_handle_t *m_handle = nullptr; //< the actual PAM handle
0045     struct pam_conv m_conv;
0046 
0047     bool m_unavailable = false;
0048     bool m_inAuthenticate = false;
0049     int m_result = -1;
0050     QString m_service;
0051 };
0052 
0053 int PamWorker::converse(int n, const struct pam_message **msg, struct pam_response **resp, void *data)
0054 {
0055     PamWorker *c = static_cast<PamWorker *>(data);
0056 
0057     if (!resp) {
0058         return PAM_BUF_ERR;
0059     }
0060 
0061     *resp = (struct pam_response *)calloc(n, sizeof(struct pam_response));
0062 
0063     for (int i = 0; i < n; i++) {
0064         bool isSecret = false;
0065         switch (msg[i]->msg_style) {
0066         case PAM_PROMPT_ECHO_OFF: {
0067             isSecret = true;
0068             Q_FALLTHROUGH();
0069         case PAM_PROMPT_ECHO_ON:
0070             Q_EMIT c->busyChanged(false);
0071 
0072             const QString prompt = QString::fromLocal8Bit(msg[i]->msg);
0073             if (isSecret) {
0074                 Q_EMIT c->promptForSecret(prompt);
0075             } else {
0076                 Q_EMIT c->prompt(prompt);
0077             }
0078 
0079             qCDebug(KSCREENLOCKER_GREET,
0080                     "[PAM worker %s] Message: %s: %s",
0081                     qUtf8Printable(c->m_service),
0082                     (isSecret ? "Echo-off prompt" : "Echo-on prompt"),
0083                     qUtf8Printable(prompt));
0084 
0085             QByteArray response;
0086             QEventLoop e;
0087             QObject::connect(c, &PamWorker::promptResponseReceived, &e, [&](const QByteArray &_response) {
0088                 qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Received response, exiting nested event loop", qUtf8Printable(c->m_service));
0089                 response = _response;
0090                 e.exit(0);
0091             });
0092             QObject::connect(c, &PamWorker::cancelled, &e, [&]() {
0093                 qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Received cancellation, exiting with PAM_CONV_ERR", qUtf8Printable(c->m_service));
0094                 e.exit(PAM_CONV_ERR);
0095             });
0096 
0097             qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Starting nested event loop to await response", qUtf8Printable(c->m_service));
0098             int rc = e.exec();
0099             if (rc != 0) {
0100                 qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Nested event loop's exit code was not zero, bailing", qUtf8Printable(c->m_service));
0101                 return rc;
0102             }
0103 
0104             Q_EMIT c->busyChanged(true);
0105 
0106             resp[i]->resp = (char *)malloc(response.length() + 1);
0107             // on error, get rid of everything
0108             if (!resp[i]->resp) {
0109                 for (int j = 0; j < n; j++) {
0110                     free(resp[i]->resp);
0111                     resp[i]->resp = nullptr;
0112                 }
0113                 free(*resp);
0114                 *resp = nullptr;
0115                 return PAM_BUF_ERR;
0116             }
0117 
0118             memcpy(resp[i]->resp, response.constData(), response.length());
0119             resp[i]->resp[response.length()] = '\0';
0120 
0121             break;
0122         }
0123         case PAM_ERROR_MSG: {
0124             const QString error = QString::fromLocal8Bit(msg[i]->msg);
0125             qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Message: Error message: %s", qUtf8Printable(c->m_service), qUtf8Printable(error));
0126             Q_EMIT c->errorMessage(error);
0127             break;
0128         }
0129         case PAM_TEXT_INFO: {
0130             // if there's only the info message, let's predict the prompts too
0131             const QString info = QString::fromLocal8Bit(msg[i]->msg);
0132             qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Message: Info message: %s", qUtf8Printable(c->m_service), qUtf8Printable(info));
0133             Q_EMIT c->infoMessage(info);
0134             break;
0135         }
0136         default:
0137             qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Message: Unhandled message type: %d", qUtf8Printable(c->m_service), msg[i]->msg_style);
0138             break;
0139         }
0140     }
0141 
0142     return PAM_SUCCESS;
0143 }
0144 
0145 PamWorker::PamWorker()
0146     : QObject(nullptr)
0147     , m_conv({&PamWorker::converse, this})
0148 {
0149 }
0150 
0151 PamWorker::~PamWorker()
0152 {
0153     if (m_handle) {
0154         pam_end(m_handle, PAM_SUCCESS);
0155     }
0156 }
0157 
0158 void PamWorker::authenticate()
0159 {
0160     if (m_inAuthenticate || m_unavailable) {
0161         return;
0162     }
0163     m_inAuthenticate = true;
0164     Q_EMIT inAuthenticateChanged(m_inAuthenticate);
0165     qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] Authenticate: Starting authentication", qUtf8Printable(m_service));
0166     int rc = pam_authenticate(m_handle, 0); // PAM_SILENT);
0167     qCDebug(KSCREENLOCKER_GREET,
0168             "[PAM worker %s] Authenticate: Authentication done, result code: %d (%s)",
0169             qUtf8Printable(m_service),
0170             rc,
0171             pam_strerror(m_handle, rc));
0172 
0173     Q_EMIT busyChanged(false);
0174 
0175     if (rc == PAM_SUCCESS) {
0176         rc = pam_setcred(m_handle, PAM_REFRESH_CRED);
0177         /* ignore errors on refresh credentials. If this did not work we use the old ones. */
0178         Q_EMIT succeeded();
0179     } else if (rc == PAM_AUTHINFO_UNAVAIL || rc == PAM_MODULE_UNKNOWN) {
0180         m_unavailable = true;
0181         Q_EMIT unavailabilityChanged(m_unavailable);
0182     } else {
0183         Q_EMIT failed();
0184     }
0185     m_inAuthenticate = false;
0186     Q_EMIT inAuthenticateChanged(m_inAuthenticate);
0187 }
0188 
0189 static void fail_delay(int retval, unsigned usec_delay, void *appdata_ptr)
0190 {
0191     Q_UNUSED(retval);
0192     Q_UNUSED(usec_delay);
0193     Q_UNUSED(appdata_ptr);
0194 }
0195 
0196 void PamWorker::start(const QString &service, const QString &user)
0197 {
0198     m_service = service;
0199     if (user.isEmpty())
0200         m_result = pam_start(qPrintable(service), nullptr, &m_conv, &m_handle);
0201     else
0202         m_result = pam_start(qPrintable(service), qPrintable(user), &m_conv, &m_handle);
0203 
0204     // get errors quicker
0205 #ifdef PAM_FAIL_DELAY
0206     pam_set_item(m_handle, PAM_FAIL_DELAY, (void *)fail_delay);
0207 #endif
0208 
0209     if (m_result != PAM_SUCCESS) {
0210         qCWarning(KSCREENLOCKER_GREET,
0211                   "[PAM worker %s] start: error starting, result code: %d (%s)",
0212                   qUtf8Printable(m_service),
0213                   m_result,
0214                   pam_strerror(m_handle, m_result));
0215         return;
0216     } else {
0217         qCDebug(KSCREENLOCKER_GREET, "[PAM worker %s] start: successfully started", qUtf8Printable(m_service));
0218     }
0219 }
0220 
0221 PamAuthenticator::PamAuthenticator(const QString &service, const QString &user, NoninteractiveAuthenticatorTypes types, QObject *parent)
0222     : QObject(parent)
0223     , m_signalsToMembers({
0224           {QMetaMethod::fromSignal(&PamAuthenticator::prompt), m_prompt},
0225           {QMetaMethod::fromSignal(&PamAuthenticator::promptForSecret), m_promptForSecret},
0226           {QMetaMethod::fromSignal(&PamAuthenticator::infoMessage), m_infoMessage},
0227           {QMetaMethod::fromSignal(&PamAuthenticator::errorMessage), m_errorMessage},
0228       })
0229     , m_service(service)
0230     , m_authenticatorType(types)
0231     , d(new PamWorker)
0232 {
0233     d->moveToThread(&m_thread);
0234 
0235     connect(&m_thread, &QThread::finished, d, &QObject::deleteLater);
0236 
0237     connect(d, &PamWorker::busyChanged, this, &PamAuthenticator::setBusy);
0238     connect(d, &PamWorker::prompt, this, [this](const QString &msg) {
0239         m_prompt = msg;
0240         Q_EMIT prompt(msg);
0241     });
0242     connect(d, &PamWorker::promptForSecret, this, [this](const QString &msg) {
0243         m_promptForSecret = msg;
0244         Q_EMIT promptForSecret(msg);
0245     });
0246     connect(d, &PamWorker::infoMessage, this, [this](const QString &msg) {
0247         m_infoMessage = msg;
0248         Q_EMIT infoMessage(msg);
0249     });
0250     connect(d, &PamWorker::errorMessage, this, [this](const QString &msg) {
0251         m_errorMessage = msg;
0252         Q_EMIT errorMessage(msg);
0253     });
0254 
0255     connect(d, &PamWorker::inAuthenticateChanged, this, [this](bool isInAuthenticate) {
0256         m_inAuthentication = isInAuthenticate;
0257         Q_EMIT availableChanged();
0258     });
0259     connect(d, &PamWorker::unavailabilityChanged, this, [this](bool isUnavailable) {
0260         m_unavailable = isUnavailable;
0261         Q_EMIT availableChanged();
0262     });
0263 
0264     connect(d, &PamWorker::succeeded, this, [this]() {
0265         m_unlocked = true;
0266         Q_EMIT succeeded();
0267     });
0268     // Failed is not a persistent state. When a view provides authentication that will either result in failure or success,
0269     // failure simply means that the prompt is getting delayed.
0270     connect(d, &PamWorker::failed, this, &PamAuthenticator::failed);
0271 
0272     m_thread.start();
0273     init(service, user);
0274 }
0275 
0276 PamAuthenticator::~PamAuthenticator()
0277 {
0278     cancel();
0279     m_thread.quit();
0280     m_thread.wait();
0281 }
0282 
0283 void PamAuthenticator::init(const QString &service, const QString &user)
0284 {
0285     QMetaObject::invokeMethod(d, [this, service, user]() {
0286         d->start(service, user);
0287     });
0288 }
0289 
0290 bool PamAuthenticator::isBusy() const
0291 {
0292     return m_busy;
0293 }
0294 
0295 bool PamAuthenticator::isAvailable() const
0296 {
0297     return m_inAuthentication && !m_unavailable;
0298 }
0299 
0300 PamAuthenticator::NoninteractiveAuthenticatorTypes PamAuthenticator::authenticatorType() const
0301 {
0302     return m_authenticatorType;
0303 }
0304 
0305 void PamAuthenticator::setBusy(bool busy)
0306 {
0307     if (m_busy != busy) {
0308         m_busy = busy;
0309         Q_EMIT busyChanged();
0310     }
0311 }
0312 
0313 bool PamAuthenticator::isUnlocked() const
0314 {
0315     return m_unlocked;
0316 }
0317 
0318 void PamAuthenticator::tryUnlock()
0319 {
0320     m_unlocked = false;
0321     QMetaObject::invokeMethod(d, &PamWorker::authenticate);
0322 }
0323 
0324 void PamAuthenticator::respond(const QByteArray &response)
0325 {
0326     QMetaObject::invokeMethod(
0327         d,
0328         [this, response]() {
0329             Q_EMIT d->promptResponseReceived(response);
0330         },
0331         Qt::QueuedConnection);
0332 }
0333 
0334 void PamAuthenticator::cancel()
0335 {
0336     m_prompt.clear();
0337     m_promptForSecret.clear();
0338     m_infoMessage.clear();
0339     m_errorMessage.clear();
0340     QMetaObject::invokeMethod(d, &PamWorker::cancelled);
0341 }
0342 
0343 QString PamAuthenticator::getPrompt() const
0344 {
0345     return m_prompt;
0346 }
0347 
0348 QString PamAuthenticator::getPromptForSecret() const
0349 {
0350     return m_promptForSecret;
0351 }
0352 
0353 QString PamAuthenticator::getInfoMessage() const
0354 {
0355     return m_infoMessage;
0356 }
0357 
0358 QString PamAuthenticator::getErrorMessage() const
0359 {
0360     return m_errorMessage;
0361 }
0362 
0363 QString PamAuthenticator::service() const
0364 {
0365     return m_service;
0366 }
0367 
0368 #include "pamauthenticator.moc"
0369 
0370 #include "moc_pamauthenticator.cpp"