Lomiri
Loading...
Searching...
No Matches
GreeterPrivate.cpp
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
29namespace QLightDM
30{
31
32class 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
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");
52
53 connect(&futureWatcher, &QFutureWatcher<int>::finished, this, &GreeterImpl::finishPam);
54 connect(this, SIGNAL(showMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)),
55 this, SLOT(handleMessage(pam_handle *, QString, QLightDM::Greeter::MessageType)));
56 // This next connect is how we pass ResponseFutures between threads
57 connect(this, SIGNAL(showPrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
58 this, SLOT(handlePrompt(pam_handle *, QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
59 Qt::BlockingQueuedConnection);
60 }
61
62 ~GreeterImpl()
63 {
64 cancelPam();
65 }
66
67 void start(QString username)
68 {
69 // Clear out any existing PAM interactions first
70 cancelPam();
71 if (pamHandle != nullptr) {
72 // While we were cancelling pam above, we processed Qt events.
73 // Which may have allowed someone to call start() on us again.
74 // In which case, we'll bail on our current start() call.
75 // This isn't racy because it's all in the same thread.
76 return;
77 }
78
79 AppData *appData = new AppData();
80 appData->impl = this;
81
82 // Now actually start a new conversation with PAM
83 pam_conv conversation;
84 conversation.conv = converseWithPam;
85 conversation.appdata_ptr = static_cast<void*>(appData);
86
87 if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
88 appData->handle = pamHandle;
89 futureWatcher.setFuture(QtConcurrent::mapped(QList<pam_handle*>() << pamHandle, authenticateWithPam));
90 } else {
91 delete appData;
92 greeterPrivate->authenticated = false;
93 Q_EMIT greeter->showMessage(QStringLiteral("Internal error: could not start PAM authentication"), QLightDM::Greeter::MessageTypeError);
94 Q_EMIT greeter->authenticationComplete();
95 }
96 }
97
98 static int authenticateWithPam(pam_handle* const& pamHandle)
99 {
100 int pamStatus = pam_authenticate(pamHandle, 0);
101 if (pamStatus == PAM_SUCCESS) {
102 pamStatus = pam_acct_mgmt(pamHandle, 0);
103 }
104 if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
105 pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
106 }
107 if (pamStatus == PAM_SUCCESS) {
108 pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
109 }
110 return pamStatus;
111 }
112
113 static int converseWithPam(int num_msg, const pam_message** msg,
114 pam_response** resp, void* appdata_ptr)
115 {
116 if (num_msg <= 0)
117 return PAM_CONV_ERR;
118
119 auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
120 if (!tmp_response)
121 return PAM_CONV_ERR;
122
123 AppData *appData = static_cast<AppData*>(appdata_ptr);
124 GreeterImpl *impl = appData->impl;
125 pam_handle *handle = appData->handle;
126
127 int count;
128 QVector<ResponseFuture> responses;
129
130 for (count = 0; count < num_msg; ++count)
131 {
132 switch (msg[count]->msg_style)
133 {
134 case PAM_PROMPT_ECHO_ON:
135 {
136 QString message(msg[count]->msg);
137 responses.append(ResponseFuture());
138 responses.last().reportStarted();
139 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeQuestion, responses.last());
140 break;
141 }
142 case PAM_PROMPT_ECHO_OFF:
143 {
144 QString message(msg[count]->msg);
145 responses.append(ResponseFuture());
146 responses.last().reportStarted();
147 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeSecret, responses.last());
148 break;
149 }
150 case PAM_TEXT_INFO:
151 {
152 QString message(msg[count]->msg);
153 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeInfo);
154 break;
155 }
156 default:
157 {
158 QString message(msg[count]->msg);
159 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeError);
160 break;
161 }
162 }
163 }
164
165 int i = 0;
166 bool raise_error = false;
167
168 for (auto &response : responses)
169 {
170 pam_response* resp_item = &tmp_response[i++];
171 resp_item->resp_retcode = 0;
172 resp_item->resp = strdup(response.future().result().toUtf8());
173
174 if (!resp_item->resp)
175 {
176 raise_error = true;
177 break;
178 }
179 }
180
181 delete appData;
182
183 if (raise_error)
184 {
185 for (int i = 0; i < count; ++i)
186 free(tmp_response[i].resp);
187
188 free(tmp_response);
189 return PAM_CONV_ERR;
190 }
191 else
192 {
193 *resp = tmp_response;
194 return PAM_SUCCESS;
195 }
196 }
197
198public Q_SLOTS:
199 bool respond(QString response)
200 {
201 if (!futures.isEmpty()) {
202 futures.dequeue().reportFinished(&response);
203 return true;
204 } else {
205 return false;
206 }
207 }
208
209 void cancelPam()
210 {
211 if (pamHandle != nullptr) {
212 QFuture<int> pamFuture = futureWatcher.future();
213 pam_handle *handle = pamHandle;
214 pamHandle = nullptr; // to disable normal finishPam() handling
215 pamFuture.cancel();
216
217 // Note the empty loop, we just want to clear the futures queue.
218 // Any further prompts from the pam thread will be immediately
219 // responded to/dismissed in handlePrompt().
220 while (respond(QString()));
221
222 pam_end(handle, PAM_CONV_ERR);
223 }
224 }
225
226Q_SIGNALS:
227 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
228 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
229
230private Q_SLOTS:
231 void finishPam()
232 {
233 if (pamHandle == nullptr) {
234 return;
235 }
236
237 int pamStatus = futureWatcher.result();
238
239 pam_end(pamHandle, pamStatus);
240 pamHandle = nullptr;
241
242 greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
243 Q_EMIT greeter->authenticationComplete();
244 }
245
246 void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
247 {
248 if (handle != pamHandle)
249 return;
250
251 Q_EMIT greeter->showMessage(text, type);
252 }
253
254 void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
255 {
256 if (handle != pamHandle) {
257 future.reportResult(QString());
258 future.reportFinished();
259 return;
260 }
261
262 futures.enqueue(future);
263 Q_EMIT greeter->showPrompt(text, type);
264 }
265
266private:
267 Greeter *greeter;
268 GreeterPrivate *greeterPrivate;
269 pam_handle* pamHandle;
270 QFutureWatcher<int> futureWatcher;
271 QQueue<ResponseFuture> futures;
272};
273
274GreeterPrivate::GreeterPrivate(Greeter* parent)
275 : authenticated(false),
276 authenticationUser(),
277 m_impl(new GreeterImpl(parent, this)),
278 q_ptr(parent)
279{
280}
281
282GreeterPrivate::~GreeterPrivate()
283{
284 delete m_impl;
285}
286
287void GreeterPrivate::handleAuthenticate()
288{
289 m_impl->start(authenticationUser);
290}
291
292void GreeterPrivate::handleRespond(const QString &response)
293{
294 m_impl->respond(response);
295}
296
297void GreeterPrivate::cancelAuthentication()
298{
299 m_impl->cancelPam();
300}
301
302}
303
304#include "GreeterPrivate.moc"