on feea33f help sign in
unity8/plugins/LightDM/liblightdm/GreeterPrivate.cpp
290 lines 8.7 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();
73 AppData *appData = new AppData();
74 appData->impl = this;
76 // Now actually start a new conversation with PAM
77 pam_conv conversation;
78 conversation.conv = converseWithPam;
79 conversation.appdata_ptr = static_cast<void*>(appData);
81 if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
82 appData->handle = pamHandle;
83 futureWatcher.setFuture(QtConcurrent::mapped(QList<pam_handle*>() << pamHandle, authenticateWithPam));
84 } else {
85 delete appData;
86 greeterPrivate->authenticated = false;
87 Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
88 Q_EMIT greeter->authenticationComplete();
89 }
90 }
92 static int authenticateWithPam(pam_handle* const& pamHandle)
93 {
94 int pamStatus = pam_authenticate(pamHandle, 0);
95 if (pamStatus == PAM_SUCCESS) {
96 pamStatus = pam_acct_mgmt(pamHandle, 0);
97 }
98 if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
99 pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
100 }
101 if (pamStatus == PAM_SUCCESS) {
102 pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
103 }
104 return pamStatus;
105 }
107 static int converseWithPam(int num_msg, const pam_message** msg,
108 pam_response** resp, void* appdata_ptr)
109 {
110 if (num_msg <= 0)
111 return PAM_CONV_ERR;
113 auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
114 if (!tmp_response)
115 return PAM_CONV_ERR;
117 AppData *appData = static_cast<AppData*>(appdata_ptr);
118 GreeterImpl *impl = appData->impl;
119 pam_handle *handle = appData->handle;
121 int count;
122 QVector<ResponseFuture> responses;
124 for (count = 0; count < num_msg; ++count)
125 {
126 switch (msg[count]->msg_style)
127 {
128 case PAM_PROMPT_ECHO_ON:
129 {
130 QString message(msg[count]->msg);
131 responses.append(ResponseFuture());
132 responses.last().reportStarted();
133 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeQuestion, responses.last());
134 break;
135 }
136 case PAM_PROMPT_ECHO_OFF:
137 {
138 QString message(msg[count]->msg);
139 responses.append(ResponseFuture());
140 responses.last().reportStarted();
141 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeSecret, responses.last());
142 break;
143 }
144 case PAM_TEXT_INFO:
145 {
146 QString message(msg[count]->msg);
147 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeInfo);
148 break;
149 }
150 default:
151 {
152 QString message(msg[count]->msg);
153 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeError);
154 break;
155 }
156 }
157 }
159 int i = 0;
160 bool raise_error = false;
162 for (auto &response : responses)
163 {
164 pam_response* resp_item = &tmp_response[i++];
165 resp_item->resp_retcode = 0;
166 resp_item->resp = strdup(response.future().result().toUtf8());
168 if (!resp_item->resp)
169 {
170 raise_error = true;
171 break;
172 }
173 }
175 delete appData;
177 if (raise_error)
178 {
179 for (int i = 0; i < count; ++i)
180 free(tmp_response[i].resp);
182 free(tmp_response);
183 return PAM_CONV_ERR;
184 }
185 else
186 {
187 *resp = tmp_response;
188 return PAM_SUCCESS;
189 }
190 }
192public Q_SLOTS:
193 bool respond(QString response)
194 {
195 if (!futures.isEmpty()) {
196 futures.dequeue().reportFinished(&response);
197 return true;
198 } else {
199 return false;
200 }
201 }
203Q_SIGNALS:
204 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
205 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
207private Q_SLOTS:
208 void finishPam()
209 {
210 if (pamHandle == nullptr) {
211 return;
212 }
214 int pamStatus = futureWatcher.result();
216 pam_end(pamHandle, pamStatus);
217 pamHandle = nullptr;
219 greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
220 Q_EMIT greeter->authenticationComplete();
221 }
223 void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
224 {
225 if (handle != pamHandle)
226 return;
228 Q_EMIT greeter->showMessage(text, type);
229 }
231 void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
232 {
233 if (handle != pamHandle) {
234 future.reportResult(QString());
235 future.reportFinished();
236 return;
237 }
239 futures.enqueue(future);
240 Q_EMIT greeter->showPrompt(text, type);
241 }
243private:
244 void cancelPam()
245 {
246 if (pamHandle != nullptr) {
247 pam_handle *handle = pamHandle;
248 pamHandle = nullptr; // to disable normal finishPam() handling
249 futureWatcher.future().cancel();
250 while (!futureWatcher.future().isFinished()) {
251 QCoreApplication::processEvents(); // let signal/slot handling happen
252 respond(QString());
253 }
254 pam_end(handle, PAM_CONV_ERR);
255 }
256 }
258 Greeter *greeter;
259 GreeterPrivate *greeterPrivate;
260 pam_handle* pamHandle;
261 QFutureWatcher<int> futureWatcher;
262 QQueue<ResponseFuture> futures;
263};
265GreeterPrivate::GreeterPrivate(Greeter* parent)
266 : authenticated(false),
267 authenticationUser(),
268 m_impl(new GreeterImpl(parent, this)),
269 q_ptr(parent)
273GreeterPrivate::~GreeterPrivate()
275 delete m_impl;
278void GreeterPrivate::handleAuthenticate()
280 m_impl->start(authenticationUser);
283void GreeterPrivate::handleRespond(const QString &response)
285 m_impl->respond(response);
290#include "GreeterPrivate.moc"