@@ -0,0 +1,305 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#include "Greeter.h"
20+#include "GreeterPrivate.h"
21+#include <QFuture>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
28+
29+namespace QLightDM
30+{
31+
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ struct AppData
37+ {
38+ GreeterImpl *impl;
39+ pam_handle *handle;
40+ };
41+
42+ typedef QFutureInterface<QString> ResponseFuture;
43+
44+public:
45+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
46+ : QObject(parent),
47+ greeter(parent),
48+ greeterPrivate(greeterPrivate),
49+ pamHandle(nullptr)
50+ {
51+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
52+
53+ connect(&futureWatcher, &QFutureWatcher<int>::finished,
54+ this, &GreeterImpl::finishPam);
55+ connect(this, SIGNAL(showMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)),
56+ this, SLOT(handleMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)));
57+ // This next connect is how we pass ResponseFutures between threads
58+ connect(this, SIGNAL(showPrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
59+ this, SLOT(handlePrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
60+ Qt::BlockingQueuedConnection);
61+ }
62+
63+ ~GreeterImpl()
64+ {
65+ cancelPam();
66+ }
67+
68+ void start(QString username)
69+ {
70+ // Clear out any existing PAM interactions first
71+ cancelPam();
72+ if (pamHandle != nullptr) {
73+ // While we were cancelling pam above, we processed Qt events.
74+ // Which may have allowed someone to call start() on us again.
75+ // In which case, we'll bail on our current start() call.
76+ // This isn't racy because it's all in the same thread.
77+ return;
78+ }
79+
80+ AppData *appData = new AppData();
81+ appData->impl = this;
82+
83+ // Now actually start a new conversation with PAM
84+ pam_conv conversation;
85+ conversation.conv = converseWithPam;
86+ conversation.appdata_ptr = static_cast<void*>(appData);
87+
88+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
89+ appData->handle = pamHandle;
90+ futureWatcher.setFuture(QtConcurrent::mapped(QList<pam_handle*>() << pamHandle, authenticateWithPam));
91+ } else {
92+ delete appData;
93+ greeterPrivate->authenticated = false;
94+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
95+ Q_EMIT greeter->authenticationComplete();
96+ }
97+ }
98+
99+ static int authenticateWithPam(pam_handle* const& pamHandle)
100+ {
101+ int pamStatus = pam_authenticate(pamHandle, 0);
102+ if (pamStatus == PAM_SUCCESS) {
103+ pamStatus = pam_acct_mgmt(pamHandle, 0);
104+ }
105+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
106+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
107+ }
108+ if (pamStatus == PAM_SUCCESS) {
109+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
110+ }
111+ return pamStatus;
112+ }
113+
114+ static int converseWithPam(int num_msg, const pam_message** msg,
115+ pam_response** resp, void* appdata_ptr)
116+ {
117+ if (num_msg <= 0)
118+ return PAM_CONV_ERR;
119+
120+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
121+ if (!tmp_response)
122+ return PAM_CONV_ERR;
123+
124+ AppData *appData = static_cast<AppData*>(appdata_ptr);
125+ GreeterImpl *impl = appData->impl;
126+ pam_handle *handle = appData->handle;
127+
128+ int count;
129+ QVector<ResponseFuture> responses;
130+
131+ for (count = 0; count < num_msg; ++count)
132+ {
133+ switch (msg[count]->msg_style)
134+ {
135+ case PAM_PROMPT_ECHO_ON:
136+ {
137+ QString message(msg[count]->msg);
138+ responses.append(ResponseFuture());
139+ responses.last().reportStarted();
140+ Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeQuestion, responses.last());
141+ break;
142+ }
143+ case PAM_PROMPT_ECHO_OFF:
144+ {
145+ QString message(msg[count]->msg);
146+ responses.append(ResponseFuture());
147+ responses.last().reportStarted();
148+ Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeSecret, responses.last());
149+ break;
150+ }
151+ case PAM_TEXT_INFO:
152+ {
153+ QString message(msg[count]->msg);
154+ Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeInfo);
155+ break;
156+ }
157+ default:
158+ {
159+ QString message(msg[count]->msg);
160+ Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeError);
161+ break;
162+ }
163+ }
164+ }
165+
166+ int i = 0;
167+ bool raise_error = false;
168+
169+ for (auto &response : responses)
170+ {
171+ pam_response* resp_item = &tmp_response[i++];
172+ resp_item->resp_retcode = 0;
173+ resp_item->resp = strdup(response.future().result().toUtf8());
174+
175+ if (!resp_item->resp)
176+ {
177+ raise_error = true;
178+ break;
179+ }
180+ }
181+
182+ delete appData;
183+
184+ if (raise_error)
185+ {
186+ for (int i = 0; i < count; ++i)
187+ free(tmp_response[i].resp);
188+
189+ free(tmp_response);
190+ return PAM_CONV_ERR;
191+ }
192+ else
193+ {
194+ *resp = tmp_response;
195+ return PAM_SUCCESS;
196+ }
197+ }
198+
199+public Q_SLOTS:
200+ bool respond(QString response)
201+ {
202+ if (!futures.isEmpty()) {
203+ futures.dequeue().reportFinished(&response);
204+ return true;
205+ } else {
206+ return false;
207+ }
208+ }
209+
210+Q_SIGNALS:
211+ void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
212+ void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
213+
214+private Q_SLOTS:
215+ void finishPam()
216+ {
217+ if (pamHandle == nullptr) {
218+ return;
219+ }
220+
221+ int pamStatus = futureWatcher.result();
222+
223+ pam_end(pamHandle, pamStatus);
224+ pamHandle = nullptr;
225+
226+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
227+ Q_EMIT greeter->authenticationComplete();
228+ }
229+
230+ void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
231+ {
232+ if (handle != pamHandle)
233+ return;
234+
235+ Q_EMIT greeter->showMessage(text, type);
236+ }
237+
238+ void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
239+ {
240+ if (handle != pamHandle) {
241+ future.reportResult(QString());
242+ future.reportFinished();
243+ return;
244+ }
245+
246+ futures.enqueue(future);
247+ Q_EMIT greeter->showPrompt(text, type);
248+ }
249+
250+private:
251+ void cancelPam()
252+ {
253+ if (pamHandle != nullptr) {
254+ QFuture<int> pamFuture = futureWatcher.future();
255+ pam_handle *handle = pamHandle;
256+ pamHandle = nullptr; // to disable normal finishPam() handling
257+ pamFuture.cancel();
258+
259+ // Note the empty loop, we just want to clear the futures queue.
260+ // Any further prompts from the pam thread will be immediately
261+ // responded to/dismissed in handlePrompt().
262+ while (respond(QString()));
263+
264+ // Now let signal/slot handling happen so the thread can finish
265+ while (!pamFuture.isFinished()) {
266+ QCoreApplication::processEvents();
267+ }
268+
269+ pam_end(handle, PAM_CONV_ERR);
270+ }
271+ }
272+
273+ Greeter *greeter;
274+ GreeterPrivate *greeterPrivate;
275+ pam_handle* pamHandle;
276+ QFutureWatcher<int> futureWatcher;
277+ QQueue<ResponseFuture> futures;
278+};
279+
280+GreeterPrivate::GreeterPrivate(Greeter* parent)
281+ : authenticated(false),
282+ authenticationUser(),
283+ m_impl(new GreeterImpl(parent, this)),
284+ q_ptr(parent)
285+{
286+}
287+
288+GreeterPrivate::~GreeterPrivate()
289+{
290+ delete m_impl;
291+}
292+
293+void GreeterPrivate::handleAuthenticate()
294+{
295+ m_impl->start(authenticationUser);
296+}
297+
298+void GreeterPrivate::handleRespond(const QString &response)
299+{
300+ m_impl->respond(response);
301+}
302+
303+}
304+
305+#include "GreeterPrivate.moc"