signon  8.40
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 QString &cancelKey,
00177                           const QVariantMap &inData,
00178                           const QString &mechanism)
00179 {
00180     if (!restartIfRequired())
00181         return false;
00182 
00183     m_isResultObtained = false;
00184     m_cancelKey = cancelKey;
00185     QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
00186     m_uiPolicy = value.toInt();
00187 
00188     QDataStream in(m_process);
00189     in << (quint32)PLUGIN_OP_PROCESS;
00190     in << mechanism;
00191 
00192     m_blobIOHandler->sendData(inData);
00193 
00194     m_isProcessing = true;
00195     return true;
00196 }
00197 
00198 bool PluginProxy::processUi(const QString &cancelKey, const QVariantMap &inData)
00199 {
00200     TRACE();
00201 
00202     if (!restartIfRequired())
00203         return false;
00204 
00205     m_cancelKey = cancelKey;
00206 
00207     QDataStream in(m_process);
00208 
00209     in << (quint32)PLUGIN_OP_PROCESS_UI;
00210 
00211     m_blobIOHandler->sendData(inData);
00212 
00213     m_isProcessing = true;
00214 
00215     return true;
00216 }
00217 
00218 bool PluginProxy::processRefresh(const QString &cancelKey,
00219                                  const QVariantMap &inData)
00220 {
00221     TRACE();
00222 
00223     if (!restartIfRequired())
00224         return false;
00225 
00226     m_cancelKey = cancelKey;
00227 
00228     QDataStream in(m_process);
00229 
00230     in << (quint32)PLUGIN_OP_REFRESH;
00231 
00232     m_blobIOHandler->sendData(inData);
00233 
00234     m_isProcessing = true;
00235 
00236     return true;
00237 }
00238 
00239 void PluginProxy::cancel()
00240 {
00241     TRACE();
00242     QDataStream in(m_process);
00243     in << (quint32)PLUGIN_OP_CANCEL;
00244 }
00245 
00246 void PluginProxy::stop()
00247 {
00248     TRACE();
00249     QDataStream in(m_process);
00250     in << (quint32)PLUGIN_OP_STOP;
00251 }
00252 
00253 bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
00254 {
00255     bool ready = m_process->waitForReadyRead(timeout);
00256 
00257     if (ready) {
00258         if (!m_process->bytesAvailable())
00259             return false;
00260 
00261         while (m_process->bytesAvailable())
00262             buffer += m_process->readAllStandardOutput();
00263     }
00264 
00265     return ready;
00266 }
00267 
00268 bool PluginProxy::isProcessing()
00269 {
00270     return m_isProcessing;
00271 }
00272 
00273 void PluginProxy::blobIOError()
00274 {
00275     TRACE();
00276     disconnect(m_blobIOHandler, SIGNAL(error()), this, SLOT(blobIOError()));
00277     stop();
00278 
00279     connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
00280     emit processError(
00281         m_cancelKey,
00282         (int)Error::InternalServer,
00283         QLatin1String("Failed to I/O session data to/from the authentication "
00284                       "plugin."));
00285 }
00286 
00287 bool PluginProxy::isResultOperationCodeValid(const int opCode) const
00288 {
00289     if (opCode == PLUGIN_RESPONSE_RESULT
00290         || opCode == PLUGIN_RESPONSE_STORE
00291         || opCode == PLUGIN_RESPONSE_ERROR
00292         || opCode == PLUGIN_RESPONSE_SIGNAL
00293         || opCode == PLUGIN_RESPONSE_UI
00294         || opCode == PLUGIN_RESPONSE_REFRESHED) return true;
00295 
00296     return false;
00297 }
00298 
00299 void PluginProxy::onReadStandardOutput()
00300 {
00301     disconnect(m_process, SIGNAL(readyRead()),
00302                this, SLOT(onReadStandardOutput()));
00303 
00304     if (!m_process->bytesAvailable()) {
00305         qCritical() << "No information available on process";
00306         m_isProcessing = false;
00307         emit processError(m_cancelKey, Error::InternalServer, QString());
00308         return;
00309     }
00310 
00311     QDataStream reader(m_process);
00312     reader >> m_currentResultOperation;
00313 
00314     TRACE() << "PROXY RESULT OPERATION:" << m_currentResultOperation;
00315 
00316     if (!isResultOperationCodeValid(m_currentResultOperation)) {
00317         TRACE() << "Unknown operation code - skipping.";
00318 
00319         //flushing the stdin channel
00320         Q_UNUSED(m_process->readAllStandardOutput());
00321 
00322         connect(m_process, SIGNAL(readyRead()),
00323                 this, SLOT(onReadStandardOutput()));
00324         return;
00325     }
00326 
00327     if (m_currentResultOperation != PLUGIN_RESPONSE_SIGNAL &&
00328         m_currentResultOperation != PLUGIN_RESPONSE_ERROR) {
00329 
00330         connect(m_blobIOHandler, SIGNAL(error()),
00331                 this, SLOT(blobIOError()));
00332 
00333         int expectedDataSize = 0;
00334         reader >> expectedDataSize;
00335         TRACE() << "PROXY EXPECTED DATA SIZE:" << expectedDataSize;
00336 
00337         m_blobIOHandler->receiveData(expectedDataSize);
00338     } else {
00339         handlePluginResponse(m_currentResultOperation);
00340     }
00341 }
00342 
00343 void PluginProxy::sessionDataReceived(const QVariantMap &map)
00344 {
00345     handlePluginResponse(m_currentResultOperation, map);
00346 }
00347 
00348 void PluginProxy::handlePluginResponse(const quint32 resultOperation,
00349                                        const QVariantMap &sessionDataMap)
00350 {
00351     TRACE() << resultOperation;
00352 
00353     if (resultOperation == PLUGIN_RESPONSE_RESULT) {
00354         TRACE() << "PLUGIN_RESPONSE_RESULT";
00355 
00356         m_isProcessing = false;
00357 
00358         if (!m_isResultObtained)
00359             emit processResultReply(m_cancelKey, sessionDataMap);
00360         else
00361             BLAME() << "Unexpected plugin response: ";
00362 
00363         m_isResultObtained = true;
00364     } else if (resultOperation == PLUGIN_RESPONSE_STORE) {
00365         TRACE() << "PLUGIN_RESPONSE_STORE";
00366 
00367         if (!m_isResultObtained)
00368             emit processStore(m_cancelKey, sessionDataMap);
00369         else
00370             BLAME() << "Unexpected plugin store: ";
00371 
00372     } else if (resultOperation == PLUGIN_RESPONSE_UI) {
00373         TRACE() << "PLUGIN_RESPONSE_UI";
00374 
00375         if (!m_isResultObtained) {
00376             bool allowed = true;
00377 
00378             if (m_uiPolicy == NoUserInteractionPolicy)
00379                 allowed = false;
00380 
00381             if (m_uiPolicy == ValidationPolicy) {
00382                 bool credentialsQueried =
00383                     (sessionDataMap.contains(SSOUI_KEY_QUERYUSERNAME)
00384                     || sessionDataMap.contains(SSOUI_KEY_QUERYPASSWORD));
00385 
00386                 bool captchaQueried  =
00387                     (sessionDataMap.contains(SSOUI_KEY_CAPTCHAIMG)
00388                      || sessionDataMap.contains(SSOUI_KEY_CAPTCHAURL));
00389 
00390                 if (credentialsQueried && !captchaQueried)
00391                     allowed = false;
00392             }
00393 
00394             if (!allowed) {
00395                 //set error and return;
00396                 TRACE() << "ui policy prevented ui launch";
00397 
00398                 QVariantMap nonConstMap = sessionDataMap;
00399                 nonConstMap.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
00400                 processUi(m_cancelKey, nonConstMap);
00401             } else {
00402                 TRACE() << "open ui";
00403                 emit processUiRequest(m_cancelKey, sessionDataMap);
00404             }
00405         } else {
00406             BLAME() << "Unexpected plugin ui response: ";
00407         }
00408     } else if (resultOperation == PLUGIN_RESPONSE_REFRESHED) {
00409         TRACE() << "PLUGIN_RESPONSE_REFRESHED";
00410 
00411         if (!m_isResultObtained)
00412             emit processRefreshRequest(m_cancelKey, sessionDataMap);
00413         else
00414             BLAME() << "Unexpected plugin ui response: ";
00415     } else if (resultOperation == PLUGIN_RESPONSE_ERROR) {
00416         TRACE() << "PLUGIN_RESPONSE_ERROR";
00417         quint32 err;
00418         QString errorMessage;
00419 
00420         QDataStream stream(m_process);
00421         stream >> err;
00422         stream >> errorMessage;
00423         m_isProcessing = false;
00424 
00425         if (!m_isResultObtained)
00426             emit processError(m_cancelKey, (int)err, errorMessage);
00427         else
00428             BLAME() << "Unexpected plugin error: " << errorMessage;
00429 
00430         m_isResultObtained = true;
00431     } else if (resultOperation == PLUGIN_RESPONSE_SIGNAL) {
00432         TRACE() << "PLUGIN_RESPONSE_SIGNAL";
00433         quint32 state;
00434         QString message;
00435 
00436         QDataStream stream(m_process);
00437         stream >> state;
00438         stream >> message;
00439 
00440         if (!m_isResultObtained)
00441             emit stateChanged(m_cancelKey, (int)state, message);
00442         else
00443             BLAME() << "Unexpected plugin signal: " << state << message;
00444     }
00445 
00446     connect(m_process, SIGNAL(readyRead()), this, SLOT(onReadStandardOutput()));
00447 }
00448 
00449 void PluginProxy::onReadStandardError()
00450 {
00451     QString ba = QString::fromLatin1(m_process->readAllStandardError());
00452 }
00453 
00454 void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
00455 {
00456     TRACE() << "Plugin process exit with code " << exitCode <<
00457         " : " << exitStatus;
00458 
00459     if (m_isProcessing || exitStatus == QProcess::CrashExit) {
00460         qCritical() << "Challenge produces CRASH!";
00461         emit processError(m_cancelKey, Error::InternalServer,
00462                           QLatin1String("plugin processed crashed"));
00463     }
00464     if (exitCode == 2) {
00465         TRACE() << "plugin process terminated because cannot change user";
00466     }
00467 
00468     m_isProcessing = false;
00469 }
00470 
00471 void PluginProxy::onError(QProcess::ProcessError err)
00472 {
00473     TRACE() << "Error: " << err;
00474 }
00475 
00476 QString PluginProxy::queryType()
00477 {
00478     TRACE();
00479 
00480     if (!restartIfRequired())
00481         return QString();
00482 
00483     QDataStream ds(m_process);
00484     ds << (quint32)PLUGIN_OP_TYPE;
00485 
00486     QByteArray buffer;
00487     bool result;
00488 
00489     if (!(result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT)))
00490         qCritical("PluginProxy returned NULL result");
00491 
00492     QString type;
00493     QDataStream out(buffer);
00494     out >> type;
00495     return type;
00496 }
00497 
00498 QStringList PluginProxy::queryMechanisms()
00499 {
00500     TRACE();
00501 
00502     if (!restartIfRequired())
00503         return QStringList();
00504 
00505     QDataStream in(m_process);
00506     in << (quint32)PLUGIN_OP_MECHANISMS;
00507 
00508     QByteArray buffer;
00509     QStringList strList;
00510     bool result;
00511 
00512     if ((result = readOnReady(buffer, PLUGINPROCESS_START_TIMEOUT))) {
00513 
00514         QVariant mechanismsVar;
00515         QDataStream out(buffer);
00516 
00517         out >> mechanismsVar;
00518         QVariantList varList = mechanismsVar.toList();
00519 
00520         for (int i = 0; i < varList.count(); i++)
00521                 strList << varList.at(i).toString();
00522 
00523         TRACE() << strList;
00524     } else
00525         qCritical("PluginProxy returned NULL result");
00526 
00527     return strList;
00528 }
00529 
00530 bool PluginProxy::waitForStarted(int timeout)
00531 {
00532     if (!m_process->waitForStarted(timeout))
00533         return false;
00534 
00535     m_blobIOHandler = new BlobIOHandler(m_process, m_process, this);
00536 
00537     connect(m_blobIOHandler,
00538             SIGNAL(dataReceived(const QVariantMap &)),
00539             this,
00540             SLOT(sessionDataReceived(const QVariantMap &)));
00541 
00542     QSocketNotifier *readNotifier =
00543         new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
00544 
00545     readNotifier->setEnabled(false);
00546     m_blobIOHandler->setReadChannelSocketNotifier(readNotifier);
00547 
00548     return true;
00549 }
00550 
00551 bool PluginProxy::waitForFinished(int timeout)
00552 {
00553     return m_process->waitForFinished(timeout);
00554 }
00555 
00556 bool PluginProxy::restartIfRequired()
00557 {
00558     if (m_process->state() == QProcess::NotRunning) {
00559         TRACE() << "RESTART REQUIRED";
00560         m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));
00561 
00562         QByteArray tmp;
00563         if (!waitForStarted(PLUGINPROCESS_START_TIMEOUT) ||
00564             !readOnReady(tmp, PLUGINPROCESS_START_TIMEOUT))
00565             return false;
00566     }
00567     return true;
00568 }
00569 
00570 } //namespace SignonDaemonNS