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"