signon  8.42
pluginproxy.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  *
6  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1 as published by the Free Software Foundation.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  */
22 
23 #include "pluginproxy.h"
24 
25 #include <sys/types.h>
26 #include <pwd.h>
27 #include <unistd.h>
28 
29 #include <QStringList>
30 #include <QThreadStorage>
31 #include <QThread>
32 #include <QDataStream>
33 
34 #include "signond-common.h"
35 #include "SignOn/uisessiondata_priv.h"
36 #include "SignOn/signonplugincommon.h"
37 
38 /*
39  * TODO: remove the "SignOn/authpluginif.h" include below after the removal
40  * of the deprecated error handling (needed here only for the deprecated
41  * AuthPluginError::PLUGIN_ERROR_GENERAL).
42  */
43 #include "SignOn/authpluginif.h"
44 
45 // signon-plugins-common
46 #include "SignOn/blobiohandler.h"
47 #include "SignOn/ipc.h"
48 
49 using namespace SignOn;
50 
51 #define REMOTEPLUGIN_BIN_PATH QLatin1String("signonpluginprocess")
52 #define PLUGINPROCESS_START_TIMEOUT 5000
53 #define PLUGINPROCESS_STOP_TIMEOUT 1000
54 
55 using namespace SignOn;
56 
57 namespace SignonDaemonNS {
58 
59 /* ---------------------- PluginProcess ---------------------- */
60 
61 PluginProcess::PluginProcess(QObject *parent):
62  QProcess(parent)
63 {
64 }
65 
66 PluginProcess::~PluginProcess()
67 {
68 }
69 
70 /* ---------------------- PluginProxy ---------------------- */
71 
72 PluginProxy::PluginProxy(QString type, QObject *parent):
73  QObject(parent)
74 {
75  TRACE();
76 
77  m_type = type;
78  m_isProcessing = false;
79  m_isResultObtained = false;
80  m_currentResultOperation = -1;
81  m_process = new PluginProcess(this);
82 
83 #ifdef SIGNOND_TRACE
84  if (criticalsEnabled()) {
85  const char *level = debugEnabled() ? "2" : "1";
86  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
87  env.insert(QLatin1String("SSO_DEBUG"), QLatin1String(level));
88  m_process->setProcessEnvironment(env);
89  }
90 #endif
91 
92  connect(m_process, SIGNAL(readyReadStandardError()),
93  this, SLOT(onReadStandardError()));
94 
95  /*
96  * TODO: some error handling should be added here, at least remove of
97  * current request data from the top of the queue and reply an error code
98  */
99  connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
100  this, SLOT(onExit(int, QProcess::ExitStatus)));
101  connect(m_process, SIGNAL(error(QProcess::ProcessError)),
102  this, SLOT(onError(QProcess::ProcessError)));
103 }
104 
105 PluginProxy::~PluginProxy()
106 {
107  if (m_process != NULL &&
108  m_process->state() != QProcess::NotRunning)
109  {
110  if (m_isProcessing)
111  cancel();
112 
113  stop();
114 
115  /* Closing the write channel ensures that the plugin process
116  * will not get stuck on the next read.
117  */
118  m_process->closeWriteChannel();
119 
120  if (!m_process->waitForFinished(PLUGINPROCESS_STOP_TIMEOUT)) {
121  qCritical() << "The signon plugin does not react on demand to "
122  "stop: need to kill it!!!";
123  m_process->kill();
124 
125  if (!m_process->waitForFinished(PLUGINPROCESS_STOP_TIMEOUT))
126  {
127  if (m_process->pid()) {
128  qCritical() << "The signon plugin seems to ignore kill(), "
129  "killing it from command line";
130  QString killProcessCommand(QString::fromLatin1("kill -9 %1").arg(m_process->pid()));
131  QProcess::execute(killProcessCommand);
132  }
133  }
134  }
135  }
136 }
137 
138 PluginProxy* PluginProxy::createNewPluginProxy(const QString &type)
139 {
140  PluginProxy *pp = new PluginProxy(type);
141 
142  QStringList args = QStringList() << pp->m_type;
143  pp->m_process->start(REMOTEPLUGIN_BIN_PATH, args);
144 
145  QByteArray tmp;
146 
147  if (!pp->waitForStarted(PLUGINPROCESS_START_TIMEOUT)) {
148  TRACE() << "The process cannot be started";
149  delete pp;
150  return NULL;
151  }
152 
153  if (!pp->readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT)) {
154  TRACE() << "The process cannot load plugin";
155  delete pp;
156  return NULL;
157  }
158 
159  if (debugEnabled()) {
160  QString pluginType = pp->queryType();
161  if (pluginType != pp->m_type) {
162  BLAME() << QString::fromLatin1("Plugin returned type '%1', "
163  "expected '%2'").
164  arg(pluginType).arg(pp->m_type);
165  }
166  }
167  pp->m_mechanisms = pp->queryMechanisms();
168 
169  connect(pp->m_process, SIGNAL(readyRead()),
170  pp, SLOT(onReadStandardOutput()));
171 
172  TRACE() << "The process is started";
173  return pp;
174 }
175 
176 bool PluginProxy::process(const QString &cancelKey,
177  const QVariantMap &inData,
178  const QString &mechanism)
179 {
180  if (!restartIfRequired())
181  return false;
182 
183  m_isResultObtained = false;
184  m_cancelKey = cancelKey;
185  QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
186  m_uiPolicy = value.toInt();
187 
188  QDataStream in(m_process);
189  in << (quint32)PLUGIN_OP_PROCESS;
190  in << mechanism;
191 
192  m_blobIOHandler->sendData(inData);
193 
194  m_isProcessing = true;
195  return true;
196 }
197 
198 bool PluginProxy::processUi(const QString &cancelKey, const QVariantMap &inData)
199 {
200  TRACE();
201 
202  if (!restartIfRequired())
203  return false;
204 
205  m_cancelKey = cancelKey;
206 
207  QDataStream in(m_process);
208 
209  in << (quint32)PLUGIN_OP_PROCESS_UI;
210 
211  m_blobIOHandler->sendData(inData);
212 
213  m_isProcessing = true;
214 
215  return true;
216 }
217 
218 bool PluginProxy::processRefresh(const QString &cancelKey,
219  const QVariantMap &inData)
220 {
221  TRACE();
222 
223  if (!restartIfRequired())
224  return false;
225 
226  m_cancelKey = cancelKey;
227 
228  QDataStream in(m_process);
229 
230  in << (quint32)PLUGIN_OP_REFRESH;
231 
232  m_blobIOHandler->sendData(inData);
233 
234  m_isProcessing = true;
235 
236  return true;
237 }
238 
239 void PluginProxy::cancel()
240 {
241  TRACE();
242  QDataStream in(m_process);
243  in << (quint32)PLUGIN_OP_CANCEL;
244 }
245 
246 void PluginProxy::stop()
247 {
248  TRACE();
249  QDataStream in(m_process);
250  in << (quint32)PLUGIN_OP_STOP;
251 }
252 
253 bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
254 {
255  bool ready = m_process->waitForReadyRead(timeout);
256 
257  if (ready) {
258  if (!m_process->bytesAvailable())
259  return false;
260 
261  while (m_process->bytesAvailable())
262  buffer += m_process->readAllStandardOutput();
263  }
264 
265  return ready;
266 }
267 
268 bool PluginProxy::isProcessing()
269 {
270  return m_isProcessing;
271 }
272 
273 void PluginProxy::blobIOError()
274 {
275  TRACE();
276  disconnect(m_blobIOHandler, SIGNAL(error()), this, SLOT(blobIOError()));
277  stop();
278 
279  connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
280  emit processError(
281  m_cancelKey,
282  (int)Error::InternalServer,
283  QLatin1String("Failed to I/O session data to/from the authentication "
284  "plugin."));
285 }
286 
287 bool PluginProxy::isResultOperationCodeValid(const int opCode) const
288 {
289  if (opCode == PLUGIN_RESPONSE_RESULT
290  || opCode == PLUGIN_RESPONSE_STORE
291  || opCode == PLUGIN_RESPONSE_ERROR
292  || opCode == PLUGIN_RESPONSE_SIGNAL
293  || opCode == PLUGIN_RESPONSE_UI
294  || opCode == PLUGIN_RESPONSE_REFRESHED) return true;
295 
296  return false;
297 }
298 
299 void PluginProxy::onReadStandardOutput()
300 {
301  disconnect(m_process, SIGNAL(readyRead()),
302  this, SLOT(onReadStandardOutput()));
303 
304  if (!m_process->bytesAvailable()) {
305  qCritical() << "No information available on process";
306  m_isProcessing = false;
307  emit processError(m_cancelKey, Error::InternalServer, QString());
308  return;
309  }
310 
311  QDataStream reader(m_process);
312  reader >> m_currentResultOperation;
313 
314  TRACE() << "PROXY RESULT OPERATION:" << m_currentResultOperation;
315 
316  if (!isResultOperationCodeValid(m_currentResultOperation)) {
317  TRACE() << "Unknown operation code - skipping.";
318 
319  //flushing the stdin channel
320  Q_UNUSED(m_process->readAllStandardOutput());
321 
322  connect(m_process, SIGNAL(readyRead()),
323  this, SLOT(onReadStandardOutput()));
324  return;
325  }
326 
327  if (m_currentResultOperation != PLUGIN_RESPONSE_SIGNAL &&
328  m_currentResultOperation != PLUGIN_RESPONSE_ERROR) {
329 
330  connect(m_blobIOHandler, SIGNAL(error()),
331  this, SLOT(blobIOError()));
332 
333  int expectedDataSize = 0;
334  reader >> expectedDataSize;
335  TRACE() << "PROXY EXPECTED DATA SIZE:" << expectedDataSize;
336 
337  m_blobIOHandler->receiveData(expectedDataSize);
338  } else {
339  handlePluginResponse(m_currentResultOperation);
340  }
341 }
342 
343 void PluginProxy::sessionDataReceived(const QVariantMap &map)
344 {
345  handlePluginResponse(m_currentResultOperation, map);
346 }
347 
348 void PluginProxy::handlePluginResponse(const quint32 resultOperation,
349  const QVariantMap &sessionDataMap)
350 {
351  TRACE() << resultOperation;
352 
353  if (resultOperation == PLUGIN_RESPONSE_RESULT) {
354  TRACE() << "PLUGIN_RESPONSE_RESULT";
355 
356  m_isProcessing = false;
357 
358  if (!m_isResultObtained)
359  emit processResultReply(m_cancelKey, sessionDataMap);
360  else
361  BLAME() << "Unexpected plugin response: ";
362 
363  m_isResultObtained = true;
364  } else if (resultOperation == PLUGIN_RESPONSE_STORE) {
365  TRACE() << "PLUGIN_RESPONSE_STORE";
366 
367  if (!m_isResultObtained)
368  emit processStore(m_cancelKey, sessionDataMap);
369  else
370  BLAME() << "Unexpected plugin store: ";
371 
372  } else if (resultOperation == PLUGIN_RESPONSE_UI) {
373  TRACE() << "PLUGIN_RESPONSE_UI";
374 
375  if (!m_isResultObtained) {
376  bool allowed = true;
377 
378  if (m_uiPolicy == NoUserInteractionPolicy)
379  allowed = false;
380 
381  if (m_uiPolicy == ValidationPolicy) {
382  bool credentialsQueried =
383  (sessionDataMap.contains(SSOUI_KEY_QUERYUSERNAME)
384  || sessionDataMap.contains(SSOUI_KEY_QUERYPASSWORD));
385 
386  bool captchaQueried =
387  (sessionDataMap.contains(SSOUI_KEY_CAPTCHAIMG)
388  || sessionDataMap.contains(SSOUI_KEY_CAPTCHAURL));
389 
390  if (credentialsQueried && !captchaQueried)
391  allowed = false;
392  }
393 
394  if (!allowed) {
395  //set error and return;
396  TRACE() << "ui policy prevented ui launch";
397 
398  QVariantMap nonConstMap = sessionDataMap;
399  nonConstMap.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
400  processUi(m_cancelKey, nonConstMap);
401  } else {
402  TRACE() << "open ui";
403  emit processUiRequest(m_cancelKey, sessionDataMap);
404  }
405  } else {
406  BLAME() << "Unexpected plugin ui response: ";
407  }
408  } else if (resultOperation == PLUGIN_RESPONSE_REFRESHED) {
409  TRACE() << "PLUGIN_RESPONSE_REFRESHED";
410 
411  if (!m_isResultObtained)
412  emit processRefreshRequest(m_cancelKey, sessionDataMap);
413  else
414  BLAME() << "Unexpected plugin ui response: ";
415  } else if (resultOperation == PLUGIN_RESPONSE_ERROR) {
416  TRACE() << "PLUGIN_RESPONSE_ERROR";
417  quint32 err;
418  QString errorMessage;
419 
420  QDataStream stream(m_process);
421  stream >> err;
422  stream >> errorMessage;
423  m_isProcessing = false;
424 
425  if (!m_isResultObtained)
426  emit processError(m_cancelKey, (int)err, errorMessage);
427  else
428  BLAME() << "Unexpected plugin error: " << errorMessage;
429 
430  m_isResultObtained = true;
431  } else if (resultOperation == PLUGIN_RESPONSE_SIGNAL) {
432  TRACE() << "PLUGIN_RESPONSE_SIGNAL";
433  quint32 state;
434  QString message;
435 
436  QDataStream stream(m_process);
437  stream >> state;
438  stream >> message;
439 
440  if (!m_isResultObtained)
441  emit stateChanged(m_cancelKey, (int)state, message);
442  else
443  BLAME() << "Unexpected plugin signal: " << state << message;
444  }
445 
446  connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
447 }
448 
449 void PluginProxy::onReadStandardError()
450 {
451  QString ba = QString::fromLatin1(m_process->readAllStandardError());
452 }
453 
454 void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
455 {
456  TRACE() << "Plugin process exit with code " << exitCode <<
457  " : " << exitStatus;
458 
459  if (m_isProcessing || exitStatus == QProcess::CrashExit) {
460  qCritical() << "Challenge produces CRASH!";
461  emit processError(m_cancelKey, Error::InternalServer,
462  QLatin1String("plugin processed crashed"));
463  }
464  if (exitCode == 2) {
465  TRACE() << "plugin process terminated because cannot change user";
466  }
467 
468  m_isProcessing = false;
469 }
470 
471 void PluginProxy::onError(QProcess::ProcessError err)
472 {
473  TRACE() << "Error: " << err;
474 }
475 
476 QString PluginProxy::queryType()
477 {
478  TRACE();
479 
480  if (!restartIfRequired())
481  return QString();
482 
483  QDataStream ds(m_process);
484  ds << (quint32)PLUGIN_OP_TYPE;
485 
486  QByteArray buffer;
487  bool result;
488 
489  if (!(result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT)))
490  qCritical("PluginProxy returned NULL result");
491 
492  QString type;
493  QDataStream out(buffer);
494  out >> type;
495  return type;
496 }
497 
498 QStringList PluginProxy::queryMechanisms()
499 {
500  TRACE();
501 
502  if (!restartIfRequired())
503  return QStringList();
504 
505  QDataStream in(m_process);
506  in << (quint32)PLUGIN_OP_MECHANISMS;
507 
508  QByteArray buffer;
509  QStringList strList;
510  bool result;
511 
512  if ((result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT))) {
513 
514  QVariant mechanismsVar;
515  QDataStream out(buffer);
516 
517  out >> mechanismsVar;
518  QVariantList varList = mechanismsVar.toList();
519 
520  for (int i = 0; i < varList.count(); i++)
521  strList << varList.at(i).toString();
522 
523  TRACE() << strList;
524  } else
525  qCritical("PluginProxy returned NULL result");
526 
527  return strList;
528 }
529 
530 bool PluginProxy::waitForStarted(int timeout)
531 {
532  if (!m_process->waitForStarted(timeout))
533  return false;
534 
535  m_blobIOHandler = new BlobIOHandler(m_process, m_process, this);
536 
537  connect(m_blobIOHandler,
538  SIGNAL(dataReceived(const QVariantMap &)),
539  this,
540  SLOT(sessionDataReceived(const QVariantMap &)));
541 
542  QSocketNotifier *readNotifier =
543  new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
544 
545  readNotifier->setEnabled(false);
546  m_blobIOHandler->setReadChannelSocketNotifier(readNotifier);
547 
548  return true;
549 }
550 
551 bool PluginProxy::waitForFinished(int timeout)
552 {
553  return m_process->waitForFinished(timeout);
554 }
555 
556 bool PluginProxy::restartIfRequired()
557 {
558  if (m_process->state() == QProcess::NotRunning) {
559  TRACE() << "RESTART REQUIRED";
560  m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));
561 
562  QByteArray tmp;
563  if (!waitForStarted(PLUGINPROCESS_START_TIMEOUT) ||
564  !readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT))
565  return false;
566  }
567  return true;
568 }
569 
570 } //namespace SignonDaemonNS