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, 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 
198 public 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 Q_SIGNALS:
210  void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
211  void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
212 
213 private Q_SLOTS:
214  void finishPam()
215  {
216  if (pamHandle == nullptr) {
217  return;
218  }
219 
220  int pamStatus = futureWatcher.result();
221 
222  pam_end(pamHandle, pamStatus);
223  pamHandle = nullptr;
224 
225  greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
226  Q_EMIT greeter->authenticationComplete();
227  }
228 
229  void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
230  {
231  if (handle != pamHandle)
232  return;
233 
234  Q_EMIT greeter->showMessage(text, type);
235  }
236 
237  void handlePrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
238  {
239  if (handle != pamHandle) {
240  future.reportResult(QString());
241  future.reportFinished();
242  return;
243  }
244 
245  futures.enqueue(future);
246  Q_EMIT greeter->showPrompt(text, type);
247  }
248 
249 private:
250  void cancelPam()
251  {
252  if (pamHandle != nullptr) {
253  QFuture<int> pamFuture = futureWatcher.future();
254  pam_handle *handle = pamHandle;
255  pamHandle = nullptr; // to disable normal finishPam() handling
256  pamFuture.cancel();
257 
258  // Note the empty loop, we just want to clear the futures queue.
259  // Any further prompts from the pam thread will be immediately
260  // responded to/dismissed in handlePrompt().
261  while (respond(QString()));
262 
263  // Now let signal/slot handling happen so the thread can finish
264  while (!pamFuture.isFinished()) {
265  QCoreApplication::processEvents();
266  }
267 
268  pam_end(handle, PAM_CONV_ERR);
269  }
270  }
271 
272  Greeter *greeter;
273  GreeterPrivate *greeterPrivate;
274  pam_handle* pamHandle;
275  QFutureWatcher<int> futureWatcher;
276  QQueue<ResponseFuture> futures;
277 };
278 
279 GreeterPrivate::GreeterPrivate(Greeter* parent)
280  : authenticated(false),
281  authenticationUser(),
282  m_impl(new GreeterImpl(parent, this)),
283  q_ptr(parent)
284 {
285 }
286 
287 GreeterPrivate::~GreeterPrivate()
288 {
289  delete m_impl;
290 }
291 
292 void GreeterPrivate::handleAuthenticate()
293 {
294  m_impl->start(authenticationUser);
295 }
296 
297 void GreeterPrivate::handleRespond(const QString &response)
298 {
299  m_impl->respond(response);
300 }
301 
302 }
303 
304 #include "GreeterPrivate.moc"