signon  8.58
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 QVariantMap &inData,
177  const QString &mechanism)
178 {
179  if (!restartIfRequired())
180  return false;
181 
182  m_isResultObtained = false;
183  QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
184  m_uiPolicy = value.toInt();
185 
186  QDataStream in(m_process);
187  in << (quint32)PLUGIN_OP_PROCESS;
188  in << mechanism;
189 
190  m_blobIOHandler->sendData(inData);
191 
192  m_isProcessing = true;
193  return true;
194 }
195 
196 bool PluginProxy::processUi(const QVariantMap &inData)
197 {
198  TRACE();
199 
200  if (!restartIfRequired())
201  return false;
202 
203  QDataStream in(m_process);
204 
205  in << (quint32)PLUGIN_OP_PROCESS_UI;
206 
207  m_blobIOHandler->sendData(inData);
208 
209  m_isProcessing = true;
210 
211  return true;
212 }
213 
214 bool PluginProxy::processRefresh(const QVariantMap &inData)
215 {
216  TRACE();
217 
218  if (!restartIfRequired())
219  return false;
220 
221  QDataStream in(m_process);
222 
223  in << (quint32)PLUGIN_OP_REFRESH;
224 
225  m_blobIOHandler->sendData(inData);
226 
227  m_isProcessing = true;
228 
229  return true;
230 }
231 
232 void PluginProxy::cancel()
233 {
234  TRACE();
235  QDataStream in(m_process);
236  in << (quint32)PLUGIN_OP_CANCEL;
237 }
238 
239 void PluginProxy::stop()
240 {
241  TRACE();
242  QDataStream in(m_process);
243  in << (quint32)PLUGIN_OP_STOP;
244 }
245 
246 bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
247 {
248  bool ready = m_process->waitForReadyRead(timeout);
249 
250  if (ready) {
251  if (!m_process->bytesAvailable())
252  return false;
253 
254  while (m_process->bytesAvailable())
255  buffer += m_process->readAllStandardOutput();
256  }
257 
258  return ready;
259 }
260 
261 bool PluginProxy::isProcessing()
262 {
263  return m_isProcessing;
264 }
265 
266 void PluginProxy::blobIOError()
267 {
268  TRACE();
269  disconnect(m_blobIOHandler, SIGNAL(error()), this, SLOT(blobIOError()));
270  stop();
271 
272  connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
273  emit processError(
274  (int)Error::InternalServer,
275  QLatin1String("Failed to I/O session data to/from the authentication "
276  "plugin."));
277 }
278 
279 bool PluginProxy::isResultOperationCodeValid(const int opCode) const
280 {
281  if (opCode == PLUGIN_RESPONSE_RESULT
282  || opCode == PLUGIN_RESPONSE_STORE
283  || opCode == PLUGIN_RESPONSE_ERROR
284  || opCode == PLUGIN_RESPONSE_SIGNAL
285  || opCode == PLUGIN_RESPONSE_UI
286  || opCode == PLUGIN_RESPONSE_REFRESHED) return true;
287 
288  return false;
289 }
290 
291 void PluginProxy::onReadStandardOutput()
292 {
293  disconnect(m_process, SIGNAL(readyRead()),
294  this, SLOT(onReadStandardOutput()));
295 
296  if (!m_process->bytesAvailable()) {
297  qCritical() << "No information available on process";
298  m_isProcessing = false;
299  emit processError(Error::InternalServer, QString());
300  return;
301  }
302 
303  QDataStream reader(m_process);
304  reader >> m_currentResultOperation;
305 
306  TRACE() << "PROXY RESULT OPERATION:" << m_currentResultOperation;
307 
308  if (!isResultOperationCodeValid(m_currentResultOperation)) {
309  TRACE() << "Unknown operation code - skipping.";
310 
311  //flushing the stdin channel
312  Q_UNUSED(m_process->readAllStandardOutput());
313 
314  connect(m_process, SIGNAL(readyRead()),
315  this, SLOT(onReadStandardOutput()));
316  return;
317  }
318 
319  if (m_currentResultOperation != PLUGIN_RESPONSE_SIGNAL &&
320  m_currentResultOperation != PLUGIN_RESPONSE_ERROR) {
321 
322  connect(m_blobIOHandler, SIGNAL(error()),
323  this, SLOT(blobIOError()));
324 
325  int expectedDataSize = 0;
326  reader >> expectedDataSize;
327  TRACE() << "PROXY EXPECTED DATA SIZE:" << expectedDataSize;
328 
329  m_blobIOHandler->receiveData(expectedDataSize);
330  } else {
331  handlePluginResponse(m_currentResultOperation);
332  }
333 }
334 
335 void PluginProxy::sessionDataReceived(const QVariantMap &map)
336 {
337  handlePluginResponse(m_currentResultOperation, map);
338 }
339 
340 void PluginProxy::handlePluginResponse(const quint32 resultOperation,
341  const QVariantMap &sessionDataMap)
342 {
343  TRACE() << resultOperation;
344 
345  if (resultOperation == PLUGIN_RESPONSE_RESULT) {
346  TRACE() << "PLUGIN_RESPONSE_RESULT";
347 
348  m_isProcessing = false;
349 
350  if (!m_isResultObtained)
351  emit processResultReply(sessionDataMap);
352  else
353  BLAME() << "Unexpected plugin response: ";
354 
355  m_isResultObtained = true;
356  } else if (resultOperation == PLUGIN_RESPONSE_STORE) {
357  TRACE() << "PLUGIN_RESPONSE_STORE";
358 
359  if (!m_isResultObtained)
360  emit processStore(sessionDataMap);
361  else
362  BLAME() << "Unexpected plugin store: ";
363 
364  } else if (resultOperation == PLUGIN_RESPONSE_UI) {
365  TRACE() << "PLUGIN_RESPONSE_UI";
366 
367  if (!m_isResultObtained) {
368  bool allowed = true;
369 
370  if (m_uiPolicy == NoUserInteractionPolicy)
371  allowed = false;
372 
373  if (m_uiPolicy == ValidationPolicy) {
374  bool credentialsQueried =
375  (sessionDataMap.contains(SSOUI_KEY_QUERYUSERNAME)
376  || sessionDataMap.contains(SSOUI_KEY_QUERYPASSWORD));
377 
378  bool captchaQueried =
379  (sessionDataMap.contains(SSOUI_KEY_CAPTCHAIMG)
380  || sessionDataMap.contains(SSOUI_KEY_CAPTCHAURL));
381 
382  if (credentialsQueried && !captchaQueried)
383  allowed = false;
384  }
385 
386  if (!allowed) {
387  //set error and return;
388  TRACE() << "ui policy prevented ui launch";
389 
390  QVariantMap nonConstMap = sessionDataMap;
391  nonConstMap.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
392  processUi(nonConstMap);
393  } else {
394  TRACE() << "open ui";
395  emit processUiRequest(sessionDataMap);
396  }
397  } else {
398  BLAME() << "Unexpected plugin ui response: ";
399  }
400  } else if (resultOperation == PLUGIN_RESPONSE_REFRESHED) {
401  TRACE() << "PLUGIN_RESPONSE_REFRESHED";
402 
403  if (!m_isResultObtained)
404  emit processRefreshRequest(sessionDataMap);
405  else
406  BLAME() << "Unexpected plugin ui response: ";
407  } else if (resultOperation == PLUGIN_RESPONSE_ERROR) {
408  TRACE() << "PLUGIN_RESPONSE_ERROR";
409  quint32 err;
410  QString errorMessage;
411 
412  QDataStream stream(m_process);
413  stream >> err;
414  stream >> errorMessage;
415  m_isProcessing = false;
416 
417  if (!m_isResultObtained)
418  emit processError((int)err, errorMessage);
419  else
420  BLAME() << "Unexpected plugin error: " << errorMessage;
421 
422  m_isResultObtained = true;
423  } else if (resultOperation == PLUGIN_RESPONSE_SIGNAL) {
424  TRACE() << "PLUGIN_RESPONSE_SIGNAL";
425  quint32 state;
426  QString message;
427 
428  QDataStream stream(m_process);
429  stream >> state;
430  stream >> message;
431 
432  if (!m_isResultObtained)
433  emit stateChanged((int)state, message);
434  else
435  BLAME() << "Unexpected plugin signal: " << state << message;
436  }
437 
438  connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
439  if (m_process->bytesAvailable()) {
440  TRACE() << "plugin has more to read after handling a response";
441  onReadStandardOutput();
442  }
443 }
444 
445 void PluginProxy::onReadStandardError()
446 {
447  QString ba = QString::fromLatin1(m_process->readAllStandardError());
448 }
449 
450 void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
451 {
452  TRACE() << "Plugin process exit with code " << exitCode <<
453  " : " << exitStatus;
454 
455  if (m_isProcessing || exitStatus == QProcess::CrashExit) {
456  qCritical() << "Challenge produces CRASH!";
457  emit processError(Error::InternalServer,
458  QLatin1String("plugin processed crashed"));
459  }
460  if (exitCode == 2) {
461  TRACE() << "plugin process terminated because cannot change user";
462  }
463 
464  m_isProcessing = false;
465 }
466 
467 void PluginProxy::onError(QProcess::ProcessError err)
468 {
469  TRACE() << "Error: " << err;
470 }
471 
472 QString PluginProxy::queryType()
473 {
474  TRACE();
475 
476  if (!restartIfRequired())
477  return QString();
478 
479  QDataStream ds(m_process);
480  ds << (quint32)PLUGIN_OP_TYPE;
481 
482  QByteArray buffer;
483  bool result;
484 
485  if (!(result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT)))
486  qCritical("PluginProxy returned NULL result");
487 
488  QString type;
489  QDataStream out(buffer);
490  out >> type;
491  return type;
492 }
493 
494 QStringList PluginProxy::queryMechanisms()
495 {
496  TRACE();
497 
498  if (!restartIfRequired())
499  return QStringList();
500 
501  QDataStream in(m_process);
502  in << (quint32)PLUGIN_OP_MECHANISMS;
503 
504  QByteArray buffer;
505  QStringList strList;
506  bool result;
507 
508  if ((result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT))) {
509 
510  QVariant mechanismsVar;
511  QDataStream out(buffer);
512 
513  out >> mechanismsVar;
514  QVariantList varList = mechanismsVar.toList();
515 
516  for (int i = 0; i < varList.count(); i++)
517  strList << varList.at(i).toString();
518 
519  TRACE() << strList;
520  } else
521  qCritical("PluginProxy returned NULL result");
522 
523  return strList;
524 }
525 
526 bool PluginProxy::waitForStarted(int timeout)
527 {
528  if (!m_process->waitForStarted(timeout))
529  return false;
530 
531  m_blobIOHandler = new BlobIOHandler(m_process, m_process, this);
532 
533  connect(m_blobIOHandler,
534  SIGNAL(dataReceived(const QVariantMap &)),
535  this,
536  SLOT(sessionDataReceived(const QVariantMap &)));
537 
538  QSocketNotifier *readNotifier =
539  new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
540 
541  readNotifier->setEnabled(false);
542  m_blobIOHandler->setReadChannelSocketNotifier(readNotifier);
543 
544  return true;
545 }
546 
547 bool PluginProxy::waitForFinished(int timeout)
548 {
549  return m_process->waitForFinished(timeout);
550 }
551 
552 bool PluginProxy::restartIfRequired()
553 {
554  if (m_process->state() == QProcess::NotRunning) {
555  TRACE() << "RESTART REQUIRED";
556  m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));
557 
558  QByteArray tmp;
559  if (!waitForStarted(PLUGINPROCESS_START_TIMEOUT) ||
560  !readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT))
561  return false;
562  }
563  return true;
564 }
565 
566 } //namespace SignonDaemonNS
#define REMOTEPLUGIN_BIN_PATH
Definition: pluginproxy.cpp:51
#define BLAME()
Definition: debug.h:32
#define PLUGINPROCESS_STOP_TIMEOUT
Definition: pluginproxy.cpp:53
#define PLUGINPROCESS_START_TIMEOUT
Definition: pluginproxy.cpp:52
RemotePluginProcess * process
Definition: main.cpp:43
#define TRACE()
Definition: debug.h:28