on 6e7ffdf help sign in
unity8/plugins/FullLightDM/GreeterPrivate.cpp
305 lines 9.4 kB history raw
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 */
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>
29namespace QLightDM
32class GreeterImpl : public QObject
34 Q_OBJECT
36 struct AppData
37 {
38 GreeterImpl *impl;
39 pam_handle *handle;
40 };
42 typedef QFutureInterface<QString> ResponseFuture;
44public:
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");
53 connect(&futureWatcher, SIGNAL(finished()),
54 this, SLOT(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 }
63 ~GreeterImpl()
64 {
65 cancelPam();
66 }
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 }
80 AppData *appData = new AppData();
81 appData->impl = this;
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);
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 }
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 }
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;
120 auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
121 if (!tmp_response)
122 return PAM_CONV_ERR;
124 AppData *appData = static_cast<AppData*>(appdata_ptr);
125 GreeterImpl *impl = appData->impl;
126 pam_handle *handle = appData->handle;
128 int count;
129 QVector<ResponseFuture> responses;
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 }
166 int i = 0;
167 bool raise_error = false;
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());
175 if (!resp_item->resp)
176 {
177 raise_error = true;
178 break;
179 }
180 }
182 delete appData;
184 if (raise_error)
185 {
186 for (int i = 0; i < count; ++i)
187 free(tmp_response[i].resp);
189 free(tmp_response);
190 return PAM_CONV_ERR;
191 }
192 else
193 {
194 *resp = tmp_response;
195 return PAM_SUCCESS;
196 }
197 }
199public 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 }
210Q_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);
214private Q_SLOTS:
215 void finishPam()
216 {
217 if (pamHandle == nullptr) {
218 return;
219 }
221 int pamStatus = futureWatcher.result();
223 pam_end(pamHandle, pamStatus);
224 pamHandle = nullptr;
226 greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
227 Q_EMIT greeter->authenticationComplete();
228 }
230 void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
231 {
232 if (handle != pamHandle)
233 return;
235 Q_EMIT greeter->showMessage(text, type);
236 }
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 }
246 futures.enqueue(future);
247 Q_EMIT greeter->showPrompt(text, type);
248 }
250private:
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();
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()));
264 // Now let signal/slot handling happen so the thread can finish
265 while (!pamFuture.isFinished()) {
266 QCoreApplication::processEvents();
267 }
269 pam_end(handle, PAM_CONV_ERR);
270 }
271 }
273 Greeter *greeter;
274 GreeterPrivate *greeterPrivate;
275 pam_handle* pamHandle;
276 QFutureWatcher<int> futureWatcher;
277 QQueue<ResponseFuture> futures;
278};
280GreeterPrivate::GreeterPrivate(Greeter* parent)
281 : authenticated(false),
282 authenticationUser(),
283 m_impl(new GreeterImpl(parent, this)),
284 q_ptr(parent)
288GreeterPrivate::~GreeterPrivate()
290 delete m_impl;
293void GreeterPrivate::handleAuthenticate()
295 m_impl->start(authenticationUser);
298void GreeterPrivate::handleRespond(const QString &response)
300 m_impl->respond(response);
305#include "GreeterPrivate.moc"