/*
* Copyright (C) 2013 Canonical, Ltd.
*
* This program 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; version 3.
*
* This program 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 this program. If not, see .
*
* Author: Michael Terry
*/
#include "Greeter.h"
#include "GreeterPrivate.h"
#include
#include
#include
#include
#include
#include
#include
namespace QLightDM
{
class GreeterImpl : public QObject
{
Q_OBJECT
struct AppData
{
GreeterImpl *impl;
pam_handle *handle;
};
typedef QFutureInterface ResponseFuture;
public:
explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
: QObject(parent),
greeter(parent),
greeterPrivate(greeterPrivate),
pamHandle(nullptr)
{
qRegisterMetaType("QLightDM::GreeterImpl::ResponseFuture");
connect(&futureWatcher, SIGNAL(finished()),
this, SLOT(finishPam()));
connect(this, SIGNAL(showMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)),
this, SLOT(handleMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)));
// This next connect is how we pass ResponseFutures between threads
connect(this, SIGNAL(showPrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
this, SLOT(handlePrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
Qt::BlockingQueuedConnection);
}
~GreeterImpl()
{
cancelPam();
}
void start(QString username)
{
// Clear out any existing PAM interactions first
cancelPam();
if (pamHandle != nullptr) {
// While we were cancelling pam above, we processed Qt events.
// Which may have allowed someone to call start() on us again.
// In which case, we'll bail on our current start() call.
// This isn't racy because it's all in the same thread.
return;
}
AppData *appData = new AppData();
appData->impl = this;
// Now actually start a new conversation with PAM
pam_conv conversation;
conversation.conv = converseWithPam;
conversation.appdata_ptr = static_cast(appData);
if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
appData->handle = pamHandle;
futureWatcher.setFuture(QtConcurrent::mapped(QList() << pamHandle, authenticateWithPam));
} else {
delete appData;
greeterPrivate->authenticated = false;
Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
Q_EMIT greeter->authenticationComplete();
}
}
static int authenticateWithPam(pam_handle* const& pamHandle)
{
int pamStatus = pam_authenticate(pamHandle, 0);
if (pamStatus == PAM_SUCCESS) {
pamStatus = pam_acct_mgmt(pamHandle, 0);
}
if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
}
if (pamStatus == PAM_SUCCESS) {
pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
}
return pamStatus;
}
static int converseWithPam(int num_msg, const pam_message** msg,
pam_response** resp, void* appdata_ptr)
{
if (num_msg <= 0)
return PAM_CONV_ERR;
auto* tmp_response = static_cast(calloc(num_msg, sizeof(pam_response)));
if (!tmp_response)
return PAM_CONV_ERR;
AppData *appData = static_cast(appdata_ptr);
GreeterImpl *impl = appData->impl;
pam_handle *handle = appData->handle;
int count;
QVector responses;
for (count = 0; count < num_msg; ++count)
{
switch (msg[count]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
{
QString message(msg[count]->msg);
responses.append(ResponseFuture());
responses.last().reportStarted();
Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeQuestion, responses.last());
break;
}
case PAM_PROMPT_ECHO_OFF:
{
QString message(msg[count]->msg);
responses.append(ResponseFuture());
responses.last().reportStarted();
Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeSecret, responses.last());
break;
}
case PAM_TEXT_INFO:
{
QString message(msg[count]->msg);
Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeInfo);
break;
}
default:
{
QString message(msg[count]->msg);
Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeError);
break;
}
}
}
int i = 0;
bool raise_error = false;
for (auto &response : responses)
{
pam_response* resp_item = &tmp_response[i++];
resp_item->resp_retcode = 0;
resp_item->resp = strdup(response.future().result().toUtf8());
if (!resp_item->resp)
{
raise_error = true;
break;
}
}
delete appData;
if (raise_error)
{
for (int i = 0; i < count; ++i)
free(tmp_response[i].resp);
free(tmp_response);
return PAM_CONV_ERR;
}
else
{
*resp = tmp_response;
return PAM_SUCCESS;
}
}
public Q_SLOTS:
bool respond(QString response)
{
if (!futures.isEmpty()) {
futures.dequeue().reportFinished(&response);
return true;
} else {
return false;
}
}
Q_SIGNALS:
void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
private Q_SLOTS:
void finishPam()
{
if (pamHandle == nullptr) {
return;
}
int pamStatus = futureWatcher.result();
pam_end(pamHandle, pamStatus);
pamHandle = nullptr;
greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
Q_EMIT greeter->authenticationComplete();
}
void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
{
if (handle != pamHandle)
return;
Q_EMIT greeter->showMessage(text, type);
}
void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
{
if (handle != pamHandle) {
future.reportResult(QString());
future.reportFinished();
return;
}
futures.enqueue(future);
Q_EMIT greeter->showPrompt(text, type);
}
private:
void cancelPam()
{
if (pamHandle != nullptr) {
QFuture pamFuture = futureWatcher.future();
pam_handle *handle = pamHandle;
pamHandle = nullptr; // to disable normal finishPam() handling
pamFuture.cancel();
// Note the empty loop, we just want to clear the futures queue.
// Any further prompts from the pam thread will be immediately
// responded to/dismissed in handlePrompt().
while (respond(QString()));
// Now let signal/slot handling happen so the thread can finish
while (!pamFuture.isFinished()) {
QCoreApplication::processEvents();
}
pam_end(handle, PAM_CONV_ERR);
}
}
Greeter *greeter;
GreeterPrivate *greeterPrivate;
pam_handle* pamHandle;
QFutureWatcher futureWatcher;
QQueue futures;
};
GreeterPrivate::GreeterPrivate(Greeter* parent)
: authenticated(false),
authenticationUser(),
m_impl(new GreeterImpl(parent, this)),
q_ptr(parent)
{
}
GreeterPrivate::~GreeterPrivate()
{
delete m_impl;
}
void GreeterPrivate::handleAuthenticate()
{
m_impl->start(authenticationUser);
}
void GreeterPrivate::handleRespond(const QString &response)
{
m_impl->respond(response);
}
}
#include "GreeterPrivate.moc"