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, 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  }
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 
72  AppData *appData = new AppData();
73  appData->impl = this;
74 
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);
79 
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  }
90 
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  }
105 
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;
111 
112  auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
113  if (!tmp_response)
114  return PAM_CONV_ERR;
115 
116  AppData *appData = static_cast<AppData*>(appdata_ptr);
117  GreeterImpl *impl = appData->impl;
118  pam_handle *handle = appData->handle;
119 
120  int count;
121  QVector<ResponseFuture> responses;
122 
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  }
157 
158  int i = 0;
159  bool raise_error = false;
160 
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());
166 
167  if (!resp_item->resp)
168  {
169  raise_error = true;
170  break;
171  }
172  }
173 
174  delete appData;
175 
176  if (raise_error)
177  {
178  for (int i = 0; i < count; ++i)
179  free(tmp_response[i].resp);
180 
181  free(tmp_response);
182  return PAM_CONV_ERR;
183  }
184  else
185  {
186  *resp = tmp_response;
187  return PAM_SUCCESS;
188  }
189  }
190 
191 public 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  }
201 
202 Q_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);
205 
206 private Q_SLOTS:
207  void finishPam()
208  {
209  if (pamHandle == nullptr) {
210  return;
211  }
212 
213  int pamStatus = futureWatcher.result();
214 
215  pam_end(pamHandle, pamStatus);
216  pamHandle = nullptr;
217 
218  greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
219  Q_EMIT greeter->authenticationComplete();
220  }
221 
222  void handleMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type)
223  {
224  if (handle != pamHandle)
225  return;
226 
227  Q_EMIT greeter->showMessage(text, type);
228  }
229 
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  }
237 
238  futures.enqueue(future);
239  Q_EMIT greeter->showPrompt(text, type);
240  }
241 
242 private:
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  }
253 
254  Greeter *greeter;
255  GreeterPrivate *greeterPrivate;
256  pam_handle* pamHandle;
257  QFutureWatcher<int> futureWatcher;
258  QQueue<ResponseFuture> futures;
259 };
260 
261 GreeterPrivate::GreeterPrivate(Greeter* parent)
262  : authenticated(false),
263  authenticationUser(),
264  m_impl(new GreeterImpl(parent, this)),
265  q_ptr(parent)
266 {
267 }
268 
269 void GreeterPrivate::handleAuthenticate()
270 {
271  m_impl->start(authenticationUser);
272 }
273 
274 void GreeterPrivate::handleRespond(const QString &response)
275 {
276  m_impl->respond(response);
277 }
278 
279 }
280 
281 #include "GreeterPrivate.moc"