on ea25363 help sign in
unity8/plugins/LightDM/liblightdm/GreeterPrivate.cpp
281 lines 8.5 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 }
62 ~GreeterImpl()
63 {
64 cancelPam();
65 }
67 void start(QString username)
68 {
69 // Clear out any existing PAM interactions first
70 cancelPam();
72 AppData *appData = new AppData();
73 appData->impl = this;
75 // Now actually start a new conversation with PAM
76 pam_conv conversation;
77 conversation.conv = converseWithPam;
78 conversation.appdata_ptr = static_cast<void*>(appData);
80 if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
81 appData->handle = pamHandle;
82 futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
83 } else {
84 delete appData;
85 greeterPrivate->authenticated = false;
86 Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
87 Q_EMIT greeter->authenticationComplete();
88 }
89 }
91 static int authenticateWithPam(pam_handle* pamHandle)
92 {
93 int pamStatus = pam_authenticate(pamHandle, 0);
94 if (pamStatus == PAM_SUCCESS) {
95 pamStatus = pam_acct_mgmt(pamHandle, 0);
96 }
97 if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
98 pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
99 }
100 if (pamStatus == PAM_SUCCESS) {
101 pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
102 }
103 return pamStatus;
104 }
106 static int converseWithPam(int num_msg, const pam_message** msg,
107 pam_response** resp, void* appdata_ptr)
108 {
109 if (num_msg <= 0)
110 return PAM_CONV_ERR;
112 auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
113 if (!tmp_response)
114 return PAM_CONV_ERR;
116 AppData *appData = static_cast<AppData*>(appdata_ptr);
117 GreeterImpl *impl = appData->impl;
118 pam_handle *handle = appData->handle;
120 int count;
121 QVector<ResponseFuture> responses;
123 for (count = 0; count < num_msg; ++count)
124 {
125 switch (msg[count]->msg_style)
126 {
127 case PAM_PROMPT_ECHO_ON:
128 {
129 QString message(msg[count]->msg);
130 responses.append(ResponseFuture());
131 responses.last().reportStarted();
132 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeQuestion, responses.last());
133 break;
134 }
135 case PAM_PROMPT_ECHO_OFF:
136 {
137 QString message(msg[count]->msg);
138 responses.append(ResponseFuture());
139 responses.last().reportStarted();
140 Q_EMIT impl->showPrompt(handle, message, Greeter::PromptTypeSecret, responses.last());
141 break;
142 }
143 case PAM_TEXT_INFO:
144 {
145 QString message(msg[count]->msg);
146 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeInfo);
147 break;
148 }
149 default:
150 {
151 QString message(msg[count]->msg);
152 Q_EMIT impl->showMessage(handle, message, Greeter::MessageTypeError);
153 break;
154 }
155 }
156 }
158 int i = 0;
159 bool raise_error = false;
161 for (auto &response : responses)
162 {
163 pam_response* resp_item = &tmp_response[i++];
164 resp_item->resp_retcode = 0;
165 resp_item->resp = strdup(response.future().result().toUtf8());
167 if (!resp_item->resp)
168 {
169 raise_error = true;
170 break;
171 }
172 }
174 delete appData;
176 if (raise_error)
177 {
178 for (int i = 0; i < count; ++i)
179 free(tmp_response[i].resp);
181 free(tmp_response);
182 return PAM_CONV_ERR;
183 }
184 else
185 {
186 *resp = tmp_response;
187 return PAM_SUCCESS;
188 }
189 }
191public Q_SLOTS:
192 bool respond(QString response)
193 {
194 if (!futures.isEmpty()) {
195 futures.dequeue().reportFinished(&response);
196 return true;
197 } else {
198 return false;
199 }
200 }
202Q_SIGNALS:
203 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
204 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
206private Q_SLOTS:
207 void finishPam()
208 {
209 if (pamHandle == nullptr) {
210 return;
211 }
213 int pamStatus = futureWatcher.result();
215 pam_end(pamHandle, pamStatus);
216 pamHandle = nullptr;
218 greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
219 Q_EMIT greeter->authenticationComplete();
220 }
222 void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
223 {
224 if (handle != pamHandle)
225 return;
227 Q_EMIT greeter->showMessage(text, type);
228 }
230 void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
231 {
232 if (handle != pamHandle) {
233 future.reportResult(QString());
234 future.reportFinished();
235 return;
236 }
238 futures.enqueue(future);
239 Q_EMIT greeter->showPrompt(text, type);
240 }
242private:
243 void cancelPam()
244 {
245 // Unfortunately we can't simply cancel our QFuture because QtConcurrent::run doesn't support cancel
246 if (pamHandle != nullptr) {
247 pam_handle *handle = pamHandle;
248 pamHandle = nullptr; // to disable normal finishPam() handling
249 while (respond(QString())); // clear our local queue of QFutures
250 pam_end(handle, PAM_CONV_ERR);
251 }
252 }
254 Greeter *greeter;
255 GreeterPrivate *greeterPrivate;
256 pam_handle* pamHandle;
257 QFutureWatcher<int> futureWatcher;
258 QQueue<ResponseFuture> futures;
259};
261GreeterPrivate::GreeterPrivate(Greeter* parent)
262 : authenticated(false),
263 authenticationUser(),
264 m_impl(new GreeterImpl(parent, this)),
265 q_ptr(parent)
269void GreeterPrivate::handleAuthenticate()
271 m_impl->start(authenticationUser);
274void GreeterPrivate::handleRespond(const QString &response)
276 m_impl->respond(response);
281#include "GreeterPrivate.moc"