signon  8.58
pluginproxy.cpp
Go to the documentation of this file.
00001 /*
00002  * This file is part of signon
00003  *
00004  * Copyright (C) 2009-2010 Nokia Corporation.
00005  *
00006  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public License
00010  * version 2.1 as published by the Free Software Foundation.
00011  *
00012  * This library is distributed in the hope that it will be useful, but
00013  * WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00015  * Lesser General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU Lesser General Public
00018  * License along with this library; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00020  * 02110-1301 USA
00021  */
00022 
00023 #include "pluginproxy.h"
00024 
00025 #include <sys/types.h>
00026 #include <pwd.h>
00027 #include <unistd.h>
00028 
00029 #include <QStringList>
00030 #include <QThreadStorage>
00031 #include <QThread>
00032 #include <QDataStream>
00033 
00034 #include "signond-common.h"
00035 #include "SignOn/uisessiondata_priv.h"
00036 #include "SignOn/signonplugincommon.h"
00037 
00038 /*
00039  *   TODO: remove the "SignOn/authpluginif.h" include below after the removal
00040  *         of the deprecated error handling (needed here only for the deprecated
00041  *         AuthPluginError::PLUGIN_ERROR_GENERAL).
00042  */
00043 #include "SignOn/authpluginif.h"
00044 
00045 // signon-plugins-common
00046 #include "SignOn/blobiohandler.h"
00047 #include "SignOn/ipc.h"
00048 
00049 using namespace SignOn;
00050 
00051 #define REMOTEPLUGIN_BIN_PATH QLatin1String("signonpluginprocess")
00052 #define PLUGINPROCESS_START_TIMEOUT 5000
00053 #define PLUGINPROCESS_STOP_TIMEOUT 1000
00054 
00055 using namespace SignOn;
00056 
00057 namespace SignonDaemonNS {
00058 
00059 /* ---------------------- PluginProcess ---------------------- */
00060 
00061 PluginProcess::PluginProcess(QObject *parent):
00062     QProcess(parent)
00063 {
00064 }
00065 
00066 PluginProcess::~PluginProcess()
00067 {
00068 }
00069 
00070 /* ---------------------- PluginProxy ---------------------- */
00071 
00072 PluginProxy::PluginProxy(QString type, QObject *parent):
00073     QObject(parent)
00074 {
00075     TRACE();
00076 
00077     m_type = type;
00078     m_isProcessing = false;
00079     m_isResultObtained = false;
00080     m_currentResultOperation = -1;
00081     m_process = new PluginProcess(this);
00082 
00083 #ifdef SIGNOND_TRACE
00084     if (criticalsEnabled()) {
00085         const char *level = debugEnabled() ? "2" : "1";
00086         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
00087         env.insert(QLatin1String("SSO_DEBUG"), QLatin1String(level));
00088         m_process->setProcessEnvironment(env);
00089     }
00090 #endif
00091 
00092     connect(m_process, SIGNAL(readyReadStandardError()),
00093             this, SLOT(onReadStandardError()));
00094 
00095     /*
00096      * TODO: some error handling should be added here, at least remove of
00097      * current request data from the top of the queue and reply an error code
00098      */
00099     connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
00100             this, SLOT(onExit(int, QProcess::ExitStatus)));
00101     connect(m_process, SIGNAL(error(QProcess::ProcessError)),
00102             this, SLOT(onError(QProcess::ProcessError)));
00103 }
00104 
00105 PluginProxy::~PluginProxy()
00106 {
00107     if (m_process != NULL &&
00108         m_process->state() != QProcess::NotRunning)
00109     {
00110         if (m_isProcessing)
00111             cancel();
00112 
00113         stop();
00114 
00115         /* Closing the write channel ensures that the plugin process
00116          * will not get stuck on the next read.
00117          */
00118         m_process->closeWriteChannel();
00119 
00120         if (!m_process->waitForFinished(PLUGINPROCESS_STOP_TIMEOUT)) {
00121             qCritical() << "The signon plugin does not react on demand to "
00122                 "stop: need to kill it!!!";
00123             m_process->kill();
00124 
00125             if (!m_process->waitForFinished(PLUGINPROCESS_STOP_TIMEOUT))
00126             {
00127                 if (m_process->pid()) {
00128                     qCritical() << "The signon plugin seems to ignore kill(), "
00129                         "killing it from command line";
00130                     QString killProcessCommand(QString::fromLatin1("kill -9 %1").arg(m_process->pid()));
00131                     QProcess::execute(killProcessCommand);
00132                 }
00133             }
00134         }
00135     }
00136 }
00137 
00138 PluginProxy* PluginProxy::createNewPluginProxy(const QString &type)
00139 {
00140     PluginProxy *pp = new PluginProxy(type);
00141 
00142     QStringList args = QStringList() << pp->m_type;
00143     pp->m_process->start(REMOTEPLUGIN_BIN_PATH, args);
00144 
00145     QByteArray tmp;
00146 
00147     if (!pp->waitForStarted(PLUGINPROCESS_START_TIMEOUT)) {
00148         TRACE() << "The process cannot be started";
00149         delete pp;
00150         return NULL;
00151     }
00152 
00153     if (!pp->readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT)) {
00154         TRACE() << "The process cannot load plugin";
00155         delete pp;
00156         return NULL;
00157     }
00158 
00159     if (debugEnabled()) {
00160         QString pluginType = pp->queryType();
00161         if (pluginType != pp->m_type) {
00162             BLAME() << QString::fromLatin1("Plugin returned type '%1', "
00163                                            "expected '%2'").
00164                 arg(pluginType).arg(pp->m_type);
00165         }
00166     }
00167     pp->m_mechanisms = pp->queryMechanisms();
00168 
00169     connect(pp->m_process, SIGNAL(readyRead()),
00170             pp, SLOT(onReadStandardOutput()));
00171 
00172     TRACE() << "The process is started";
00173     return pp;
00174 }
00175 
00176 bool PluginProxy::process(const QVariantMap &inData,
00177                           const QString &mechanism)
00178 {
00179     if (!restartIfRequired())
00180         return false;
00181 
00182     m_isResultObtained = false;
00183     QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
00184     m_uiPolicy = value.toInt();
00185 
00186     QDataStream in(m_process);
00187     in << (quint32)PLUGIN_OP_PROCESS;
00188     in << mechanism;
00189 
00190     m_blobIOHandler->sendData(inData);
00191 
00192     m_isProcessing = true;
00193     return true;
00194 }
00195 
00196 bool PluginProxy::processUi(const QVariantMap &inData)
00197 {
00198     TRACE();
00199 
00200     if (!restartIfRequired())
00201         return false;
00202 
00203     QDataStream in(m_process);
00204 
00205     in << (quint32)PLUGIN_OP_PROCESS_UI;
00206 
00207     m_blobIOHandler->sendData(inData);
00208 
00209     m_isProcessing = true;
00210 
00211     return true;
00212 }
00213 
00214 bool PluginProxy::processRefresh(const QVariantMap &inData)
00215 {
00216     TRACE();
00217 
00218     if (!restartIfRequired())
00219         return false;
00220 
00221     QDataStream in(m_process);
00222 
00223     in << (quint32)PLUGIN_OP_REFRESH;
00224 
00225     m_blobIOHandler->sendData(inData);
00226 
00227     m_isProcessing = true;
00228 
00229     return true;
00230 }
00231 
00232 void PluginProxy::cancel()
00233 {
00234     TRACE();
00235     QDataStream in(m_process);
00236     in << (quint32)PLUGIN_OP_CANCEL;
00237 }
00238 
00239 void PluginProxy::stop()
00240 {
00241     TRACE();
00242     QDataStream in(m_process);
00243     in << (quint32)PLUGIN_OP_STOP;
00244 }
00245 
00246 bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
00247 {
00248     bool ready = m_process->waitForReadyRead(timeout);
00249 
00250     if (ready) {
00251         if (!m_process->bytesAvailable())
00252             return false;
00253 
00254         while (m_process->bytesAvailable())
00255             buffer += m_process->readAllStandardOutput();
00256     }
00257 
00258     return ready;
00259 }
00260 
00261 bool PluginProxy::isProcessing()
00262 {
00263     return m_isProcessing;
00264 }
00265 
00266 void PluginProxy::blobIOError()
00267 {
00268     TRACE();
00269     disconnect(m_blobIOHandler, SIGNAL(error()), this, SLOT(blobIOError()));
00270     stop();
00271 
00272     connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
00273     emit processError(
00274         (int)Error::InternalServer,
00275         QLatin1String("Failed to I/O session data to/from the authentication "
00276                       "plugin."));
00277 }
00278 
00279 bool PluginProxy::isResultOperationCodeValid(const int opCode) const
00280 {
00281     if (opCode == PLUGIN_RESPONSE_RESULT
00282         || opCode == PLUGIN_RESPONSE_STORE
00283         || opCode == PLUGIN_RESPONSE_ERROR
00284         || opCode == PLUGIN_RESPONSE_SIGNAL
00285         || opCode == PLUGIN_RESPONSE_UI
00286         || opCode == PLUGIN_RESPONSE_REFRESHED) return true;
00287 
00288     return false;
00289 }
00290 
00291 void PluginProxy::onReadStandardOutput()
00292 {
00293     disconnect(m_process, SIGNAL(readyRead()),
00294                this, SLOT(onReadStandardOutput()));
00295 
00296     if (!m_process->bytesAvailable()) {
00297         qCritical() << "No information available on process";
00298         m_isProcessing = false;
00299         emit processError(Error::InternalServer, QString());
00300         return;
00301     }
00302 
00303     QDataStream reader(m_process);
00304     reader >> m_currentResultOperation;
00305 
00306     TRACE() << "PROXY RESULT OPERATION:" << m_currentResultOperation;
00307 
00308     if (!isResultOperationCodeValid(m_currentResultOperation)) {
00309         TRACE() << "Unknown operation code - skipping.";
00310 
00311         //flushing the stdin channel
00312         Q_UNUSED(m_process->readAllStandardOutput());
00313 
00314         connect(m_process, SIGNAL(readyRead()),
00315                 this, SLOT(onReadStandardOutput()));
00316         return;
00317     }
00318 
00319     if (m_currentResultOperation != PLUGIN_RESPONSE_SIGNAL &&
00320         m_currentResultOperation != PLUGIN_RESPONSE_ERROR) {
00321 
00322         connect(m_blobIOHandler, SIGNAL(error()),
00323                 this, SLOT(blobIOError()));
00324 
00325         int expectedDataSize = 0;
00326         reader >> expectedDataSize;
00327         TRACE() << "PROXY EXPECTED DATA SIZE:" << expectedDataSize;
00328 
00329         m_blobIOHandler->receiveData(expectedDataSize);
00330     } else {
00331         handlePluginResponse(m_currentResultOperation);
00332     }
00333 }
00334 
00335 void PluginProxy::sessionDataReceived(const QVariantMap &map)
00336 {
00337     handlePluginResponse(m_currentResultOperation, map);
00338 }
00339 
00340 void PluginProxy::handlePluginResponse(const quint32 resultOperation,
00341                                        const QVariantMap &sessionDataMap)
00342 {
00343     TRACE() << resultOperation;
00344 
00345     if (resultOperation == PLUGIN_RESPONSE_RESULT) {
00346         TRACE() << "PLUGIN_RESPONSE_RESULT";
00347 
00348         m_isProcessing = false;
00349 
00350         if (!m_isResultObtained)
00351             emit processResultReply(sessionDataMap);
00352         else
00353             BLAME() << "Unexpected plugin response: ";
00354 
00355         m_isResultObtained = true;
00356     } else if (resultOperation == PLUGIN_RESPONSE_STORE) {
00357         TRACE() << "PLUGIN_RESPONSE_STORE";
00358 
00359         if (!m_isResultObtained)
00360             emit processStore(sessionDataMap);
00361         else
00362             BLAME() << "Unexpected plugin store: ";
00363 
00364     } else if (resultOperation == PLUGIN_RESPONSE_UI) {
00365         TRACE() << "PLUGIN_RESPONSE_UI";
00366 
00367         if (!m_isResultObtained) {
00368             bool allowed = true;
00369 
00370             if (m_uiPolicy == NoUserInteractionPolicy)
00371                 allowed = false;
00372 
00373             if (m_uiPolicy == ValidationPolicy) {
00374                 bool credentialsQueried =
00375                     (sessionDataMap.contains(SSOUI_KEY_QUERYUSERNAME)
00376                     || sessionDataMap.contains(SSOUI_KEY_QUERYPASSWORD));
00377 
00378                 bool captchaQueried  =
00379                     (sessionDataMap.contains(SSOUI_KEY_CAPTCHAIMG)
00380                      || sessionDataMap.contains(SSOUI_KEY_CAPTCHAURL));
00381 
00382                 if (credentialsQueried && !captchaQueried)
00383                     allowed = false;
00384             }
00385 
00386             if (!allowed) {
00387                 //set error and return;
00388                 TRACE() << "ui policy prevented ui launch";
00389 
00390                 QVariantMap nonConstMap = sessionDataMap;
00391                 nonConstMap.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
00392                 processUi(nonConstMap);
00393             } else {
00394                 TRACE() << "open ui";
00395                 emit processUiRequest(sessionDataMap);
00396             }
00397         } else {
00398             BLAME() << "Unexpected plugin ui response: ";
00399         }
00400     } else if (resultOperation == PLUGIN_RESPONSE_REFRESHED) {
00401         TRACE() << "PLUGIN_RESPONSE_REFRESHED";
00402 
00403         if (!m_isResultObtained)
00404             emit processRefreshRequest(sessionDataMap);
00405         else
00406             BLAME() << "Unexpected plugin ui response: ";
00407     } else if (resultOperation == PLUGIN_RESPONSE_ERROR) {
00408         TRACE() << "PLUGIN_RESPONSE_ERROR";
00409         quint32 err;
00410         QString errorMessage;
00411 
00412         QDataStream stream(m_process);
00413         stream >> err;
00414         stream >> errorMessage;
00415         m_isProcessing = false;
00416 
00417         if (!m_isResultObtained)
00418             emit processError((int)err, errorMessage);
00419         else
00420             BLAME() << "Unexpected plugin error: " << errorMessage;
00421 
00422         m_isResultObtained = true;
00423     } else if (resultOperation == PLUGIN_RESPONSE_SIGNAL) {
00424         TRACE() << "PLUGIN_RESPONSE_SIGNAL";
00425         quint32 state;
00426         QString message;
00427 
00428         QDataStream stream(m_process);
00429         stream >> state;
00430         stream >> message;
00431 
00432         if (!m_isResultObtained)
00433             emit stateChanged((int)state, message);
00434         else
00435             BLAME() << "Unexpected plugin signal: " << state << message;
00436     }
00437 
00438     connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
00439     if (m_process->bytesAvailable()) {
00440         TRACE() << "plugin has more to read after handling a response";
00441         onReadStandardOutput();
00442     }
00443 }
00444 
00445 void PluginProxy::onReadStandardError()
00446 {
00447     QString ba = QString::fromLatin1(m_process->readAllStandardError());
00448 }
00449 
00450 void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
00451 {
00452     TRACE() << "Plugin process exit with code " << exitCode <<
00453         " : " << exitStatus;
00454 
00455     if (m_isProcessing || exitStatus == QProcess::CrashExit) {
00456         qCritical() << "Challenge produces CRASH!";
00457         emit processError(Error::InternalServer,
00458                           QLatin1String("plugin processed crashed"));
00459     }
00460     if (exitCode == 2) {
00461         TRACE() << "plugin process terminated because cannot change user";
00462     }
00463 
00464     m_isProcessing = false;
00465 }
00466 
00467 void PluginProxy::onError(QProcess::ProcessError err)
00468 {
00469     TRACE() << "Error: " << err;
00470 }
00471 
00472 QString PluginProxy::queryType()
00473 {
00474     TRACE();
00475 
00476     if (!restartIfRequired())
00477         return QString();
00478 
00479     QDataStream ds(m_process);
00480     ds << (quint32)PLUGIN_OP_TYPE;
00481 
00482     QByteArray buffer;
00483     bool result;
00484 
00485     if (!(result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT)))
00486         qCritical("PluginProxy returned NULL result");
00487 
00488     QString type;
00489     QDataStream out(buffer);
00490     out >> type;
00491     return type;
00492 }
00493 
00494 QStringList PluginProxy::queryMechanisms()
00495 {
00496     TRACE();
00497 
00498     if (!restartIfRequired())
00499         return QStringList();
00500 
00501     QDataStream in(m_process);
00502     in << (quint32)PLUGIN_OP_MECHANISMS;
00503 
00504     QByteArray buffer;
00505     QStringList strList;
00506     bool result;
00507 
00508     if ((result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT))) {
00509 
00510         QVariant mechanismsVar;
00511         QDataStream out(buffer);
00512 
00513         out >> mechanismsVar;
00514         QVariantList varList = mechanismsVar.toList();
00515 
00516         for (int i = 0; i < varList.count(); i++)
00517                 strList << varList.at(i).toString();
00518 
00519         TRACE() << strList;
00520     } else
00521         qCritical("PluginProxy returned NULL result");
00522 
00523     return strList;
00524 }
00525 
00526 bool PluginProxy::waitForStarted(int timeout)
00527 {
00528     if (!m_process->waitForStarted(timeout))
00529         return false;
00530 
00531     m_blobIOHandler = new BlobIOHandler(m_process, m_process, this);
00532 
00533     connect(m_blobIOHandler,
00534             SIGNAL(dataReceived(const QVariantMap &)),
00535             this,
00536             SLOT(sessionDataReceived(const QVariantMap &)));
00537 
00538     QSocketNotifier *readNotifier =
00539         new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
00540 
00541     readNotifier->setEnabled(false);
00542     m_blobIOHandler->setReadChannelSocketNotifier(readNotifier);
00543 
00544     return true;
00545 }
00546 
00547 bool PluginProxy::waitForFinished(int timeout)
00548 {
00549     return m_process->waitForFinished(timeout);
00550 }
00551 
00552 bool PluginProxy::restartIfRequired()
00553 {
00554     if (m_process->state() == QProcess::NotRunning) {
00555         TRACE() << "RESTART REQUIRED";
00556         m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));
00557 
00558         QByteArray tmp;
00559         if (!waitForStarted(PLUGINPROCESS_START_TIMEOUT) ||
00560             !readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT))
00561             return false;
00562     }
00563     return true;
00564 }
00565 
00566 } //namespace SignonDaemonNS