signon
8.58
|
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