Unity 8
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 
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(QStringLiteral("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"