Encrypt communication with plugins
[accounts-sso:signon.git] / src / remotepluginprocess / remotepluginprocess.cpp
1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2010 Nokia Corporation.
5  *
6  * Contact: Alberto Mardegan <alberto.mardegan@nokia.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 #include <QNetworkProxy>
23 #include <QProcess>
24 #include <QUrl>
25 #include <QTimer>
26 #include <QBuffer>
27 #include <QDataStream>
28
29 #ifdef HAVE_GCONF
30 #include <gq/GConfItem>
31 #endif
32
33 #include "debug.h"
34 #include "remotepluginprocess.h"
35
36 // signon-plugins-common
37 #include "SignOn/blobiohandler.h"
38 #include "SignOn/ipc.h"
39
40 #include "SignOnCrypto/Encryptor"
41 using namespace SignOn;
42
43 namespace RemotePluginProcessNS {
44
45     static CancelEventThread *cancelThread = NULL;
46
47     /* ---------------------- RemotePluginProcess ---------------------- */
48
49     RemotePluginProcess::RemotePluginProcess(QObject *parent) : QObject(parent)
50     {
51         m_plugin = NULL;
52         m_readnotifier = NULL;
53         m_errnotifier = NULL;
54         m_encryptedInDevice = NULL;
55         m_encryptedOutDevice = NULL;
56
57         qRegisterMetaType<SignOn::SessionData>("SignOn::SessionData");
58         qRegisterMetaType<QString>("QString");
59     }
60
61     RemotePluginProcess::~RemotePluginProcess()
62     {
63         delete m_plugin;
64         delete m_readnotifier;
65         delete m_errnotifier;
66
67         if (cancelThread) {
68             cancelThread->quit();
69             cancelThread->wait();
70             delete cancelThread;
71         }
72
73         delete m_encryptedInDevice;
74         m_encryptedInDevice = NULL;
75         delete m_encryptedOutDevice;
76         m_encryptedOutDevice = NULL;
77     }
78
79     RemotePluginProcess* RemotePluginProcess::createRemotePluginProcess(QString &type, QObject *parent)
80     {
81         RemotePluginProcess *rpp = new RemotePluginProcess(parent);
82
83         //this is needed before plugin is initialized
84         rpp->setupProxySettings();
85
86         if (!rpp->loadPlugin(type) ||
87            !rpp->setupDataStreams() ||
88            rpp->m_plugin->type() != type) {
89             delete rpp;
90             return NULL;
91         }
92         return rpp;
93     }
94
95     bool RemotePluginProcess::loadPlugin(QString &type)
96     {
97         TRACE() << " loading auth library for " << type;
98
99         QLibrary lib(getPluginName(type));
100
101         if (!lib.load()) {
102             qCritical() << QString("Failed to load %1 (reason: %2)")
103                 .arg(getPluginName(type)).arg(lib.errorString());
104             return false;
105         }
106
107         TRACE() << "library loaded";
108
109         typedef AuthPluginInterface* (*SsoAuthPluginInstanceF)();
110         SsoAuthPluginInstanceF instance = (SsoAuthPluginInstanceF)lib.resolve("auth_plugin_instance");
111         if (!instance) {
112             qCritical() << QString("Failed to resolve init function in %1 (reason: %2)")
113                 .arg(getPluginName(type)).arg(lib.errorString());
114             return false;
115         }
116
117         TRACE() << "constructor resolved";
118
119         m_plugin = qobject_cast<AuthPluginInterface *>(instance());
120
121         if (!m_plugin) {
122             qCritical() << QString("Failed to cast object for %1 type")
123                 .arg(type);
124             return false;
125         }
126
127         connect(m_plugin, SIGNAL(result(const SignOn::SessionData&)),
128                   this, SLOT(result(const SignOn::SessionData&)));
129
130         connect(m_plugin, SIGNAL(store(const SignOn::SessionData&)),
131                   this, SLOT(store(const SignOn::SessionData&)));
132
133         connect(m_plugin, SIGNAL(error(const SignOn::Error &)),
134                   this, SLOT(error(const SignOn::Error &)));
135
136         connect(m_plugin, SIGNAL(userActionRequired(const SignOn::UiSessionData&)),
137                   this, SLOT(userActionRequired(const SignOn::UiSessionData&)));
138
139         connect(m_plugin, SIGNAL(refreshed(const SignOn::UiSessionData&)),
140                   this, SLOT(refreshed(const SignOn::UiSessionData&)));
141
142         connect(m_plugin, SIGNAL(statusChanged(const AuthPluginState, const QString&)),
143                   this, SLOT(statusChanged(const AuthPluginState, const QString&)));
144
145         m_plugin->setParent(this);
146
147         TRACE() << "plugin is fully initialized";
148         return true;
149     }
150
151     bool RemotePluginProcess::setupDataStreams()
152     {
153         TRACE();
154
155         m_infile.open(STDIN_FILENO, QIODevice::ReadOnly);
156         m_outfile.open(STDOUT_FILENO, QIODevice::WriteOnly);
157
158         m_readnotifier = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read);
159         m_errnotifier = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Exception);
160
161         connect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
162         connect(m_errnotifier, SIGNAL(activated(int)), this, SIGNAL(processStopped()));
163
164         QDataStream in(&m_infile);
165         QString key_and_iv_enc;
166         in >> key_and_iv_enc;
167         if (key_and_iv_enc.isEmpty()) {
168             TRACE() << "Failed to read key and iv from stdin";
169             return false;
170         }
171         SignOnCrypto::Encryptor encryptor;
172         QString key_and_iv = encryptor.decodeString(key_and_iv_enc);
173         if (key_and_iv.isEmpty()) {
174             TRACE() << "Failed to decrypt key and iv";
175             return false;
176         }
177         QByteArray raw_key_and_iv = QByteArray::fromBase64(key_and_iv.toLatin1());
178         if (raw_key_and_iv.size() != 16 + AES_BLOCK_SIZE * 2) {
179             TRACE() << "Unexpected key or iv size";
180             return false;
181         }
182
183         unsigned char key[16] = {0};
184         unsigned char iv_in[AES_BLOCK_SIZE] = {0};
185         unsigned char iv_out[AES_BLOCK_SIZE] = {0};
186         memcpy(key, raw_key_and_iv.constData(), sizeof(key));
187         memcpy(iv_in, raw_key_and_iv.constData() + sizeof(key), sizeof(iv_in));
188         memcpy(iv_out, raw_key_and_iv.constData() + sizeof(key) + sizeof(iv_in), sizeof(iv_out));
189         m_encryptedInDevice = new EncryptedDevice(&m_infile, key, sizeof(key), iv_in, iv_out);
190         m_encryptedOutDevice = new EncryptedDevice(&m_outfile, key, sizeof(key), iv_in, iv_out);
191
192         if (!cancelThread)
193             cancelThread = new CancelEventThread(m_plugin, m_encryptedInDevice);
194
195         TRACE() << "cancel thread created";
196
197         m_blobIOHandler = new BlobIOHandler(m_encryptedInDevice, m_encryptedOutDevice, this);
198
199         connect(m_blobIOHandler,
200                 SIGNAL(dataReceived(const QVariantMap &)),
201                 this,
202                 SLOT(sessionDataReceived(const QVariantMap &)));
203
204         connect(m_blobIOHandler,
205                 SIGNAL(error()),
206                 this,
207                 SLOT(blobIOError()));
208
209         m_blobIOHandler->setReadChannelSocketNotifier(m_readnotifier);
210
211         return true;
212     }
213
214     bool RemotePluginProcess::setupProxySettings()
215     {
216         TRACE();
217         //set application default proxy
218         QNetworkProxy networkProxy = QNetworkProxy::applicationProxy();
219
220 #ifdef HAVE_GCONF
221         //get proxy settings from GConf
222         GConfItem *hostItem = new GConfItem("/system/http_proxy/host");
223         if (hostItem->value().canConvert(QVariant::String)) {
224             QString host = hostItem->value().toString();
225             GConfItem *portItem = new GConfItem("/system/http_proxy/port");
226             uint port = portItem->value().toUInt();
227             networkProxy = QNetworkProxy(QNetworkProxy::HttpProxy,
228                                         host, port);
229             delete portItem;
230         }
231         delete hostItem;
232 #endif
233
234         //get system env for proxy
235         QString proxy = qgetenv("http_proxy");
236         if (!proxy.isEmpty()) {
237             QUrl proxyUrl(proxy);
238             if (!proxyUrl.host().isEmpty()) {
239                 networkProxy = QNetworkProxy(QNetworkProxy::HttpProxy,
240                                         proxyUrl.host(),
241                                         proxyUrl.port(),
242                                         proxyUrl.userName(),
243                                         proxyUrl.password());
244             }
245         }
246
247         //add other proxy types here
248
249         TRACE() << networkProxy.hostName() << ":" << networkProxy.port();
250         QNetworkProxy::setApplicationProxy(networkProxy);
251         return true;
252     }
253
254     void RemotePluginProcess::blobIOError()
255     {
256         error(
257             Error(Error::InternalServer,
258             QLatin1String("Failed to I/O session data to/from the signon daemon.")));
259         connect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
260     }
261
262     void RemotePluginProcess::result(const SignOn::SessionData &data)
263     {
264         disableCancelThread();
265         QDataStream out(m_encryptedOutDevice);
266         QVariantMap resultDataMap;
267
268         foreach(QString key, data.propertyNames())
269             resultDataMap[key] = data.getProperty(key);
270
271         out << (quint32)PLUGIN_RESPONSE_RESULT;
272
273         m_blobIOHandler->sendData(resultDataMap);
274
275         m_outfile.flush();
276     }
277
278     void RemotePluginProcess::store(const SignOn::SessionData &data)
279     {
280         QDataStream out(m_encryptedOutDevice);
281         QVariantMap storeDataMap;
282
283         foreach(QString key, data.propertyNames())
284             storeDataMap[key] = data.getProperty(key);
285
286         out << (quint32)PLUGIN_RESPONSE_STORE;
287
288         m_blobIOHandler->sendData(storeDataMap);
289
290         m_outfile.flush();
291     }
292
293     void RemotePluginProcess::error(const SignOn::Error &err)
294     {
295         disableCancelThread();
296
297         QDataStream out(m_encryptedOutDevice);
298
299         out << (quint32)PLUGIN_RESPONSE_ERROR;
300         out << (quint32)err.type();
301         out << err.message();
302         m_outfile.flush();
303
304         TRACE() << "error is sent" << err.type() << " " << err.message();
305     }
306
307     void RemotePluginProcess::userActionRequired(const SignOn::UiSessionData &data)
308     {
309         TRACE();
310         disableCancelThread();
311
312         QDataStream out(m_encryptedOutDevice);
313         QVariantMap resultDataMap;
314
315         foreach(QString key, data.propertyNames())
316             resultDataMap[key] = data.getProperty(key);
317
318         out << (quint32)PLUGIN_RESPONSE_UI;
319         m_blobIOHandler->sendData(resultDataMap);
320         m_outfile.flush();
321     }
322
323     void RemotePluginProcess::refreshed(const SignOn::UiSessionData &data)
324     {
325         TRACE();
326         disableCancelThread();
327
328         QDataStream out(m_encryptedOutDevice);
329         QVariantMap resultDataMap;
330
331         foreach(QString key, data.propertyNames())
332             resultDataMap[key] = data.getProperty(key);
333
334         m_readnotifier->setEnabled(true);
335
336         out << (quint32)PLUGIN_RESPONSE_REFRESHED;
337
338         m_blobIOHandler->sendData(resultDataMap);
339
340         m_outfile.flush();
341     }
342
343     void RemotePluginProcess::statusChanged(const AuthPluginState state, const QString &message)
344     {
345         TRACE();
346         QDataStream out(m_encryptedOutDevice);
347
348         out << (quint32)PLUGIN_RESPONSE_SIGNAL;
349         out << (quint32)state;
350         out << message;
351
352         m_outfile.flush();
353     }
354
355     QString RemotePluginProcess::getPluginName(const QString &type)
356     {
357         QString fileName = QDir::cleanPath(SIGNON_PLUGINS_DIR) +
358                            QDir::separator() +
359                            QString(SIGNON_PLUGIN_PREFIX) +
360                            type +
361                            QString(SIGNON_PLUGIN_SUFFIX);
362
363         return fileName;
364     }
365
366     void RemotePluginProcess::type()
367     {
368         QDataStream out(m_encryptedOutDevice);
369         QByteArray typeBa;
370         typeBa.append(m_plugin->type());
371         out << typeBa;
372     }
373
374     void RemotePluginProcess::mechanisms()
375     {
376         QDataStream out(m_encryptedOutDevice);
377         QStringList mechanisms = m_plugin->mechanisms();
378         QVariant mechsVar = mechanisms;
379         out << mechsVar;
380     }
381
382     void RemotePluginProcess::process()
383     {
384         QDataStream in(m_encryptedInDevice);
385
386
387         in >> m_currentMechanism;
388
389         int processBlobSize = -1;
390         in >> processBlobSize;
391
392         disconnect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
393
394         m_currentOperation = PLUGIN_OP_PROCESS;
395         m_blobIOHandler->receiveData(processBlobSize);
396     }
397
398     void RemotePluginProcess::userActionFinished()
399     {
400         QDataStream in(m_encryptedInDevice);
401         int processBlobSize = -1;
402         in >> processBlobSize;
403
404         disconnect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
405
406         m_currentOperation = PLUGIN_OP_PROCESS_UI;
407         m_blobIOHandler->receiveData(processBlobSize);
408     }
409
410     void RemotePluginProcess::refresh()
411     {
412         QDataStream in(m_encryptedInDevice);
413         int processBlobSize = -1;
414         in >> processBlobSize;
415
416         disconnect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
417
418         m_currentOperation = PLUGIN_OP_REFRESH;
419         m_blobIOHandler->receiveData(processBlobSize);
420     }
421
422     void RemotePluginProcess::sessionDataReceived(const QVariantMap &sessionDataMap)
423     {
424         enableCancelThread();
425         TRACE() << "The cancel thread is started";
426
427         if (m_currentOperation == PLUGIN_OP_PROCESS) {
428             SessionData inData(sessionDataMap);
429             m_plugin->process(inData, m_currentMechanism);
430             m_currentMechanism.clear();
431
432         } else if(m_currentOperation == PLUGIN_OP_PROCESS_UI) {
433             UiSessionData inData(sessionDataMap);
434             m_plugin->userActionFinished(inData);
435
436         } else if(m_currentOperation == PLUGIN_OP_REFRESH) {
437             UiSessionData inData(sessionDataMap);
438             m_plugin->refresh(inData);
439
440         } else {
441             TRACE() << "Wrong operation code.";
442             error(Error(Error::InternalServer,
443                         QLatin1String("Plugin process - invalid operation code.")));
444         }
445
446         m_currentOperation = PLUGIN_OP_STOP;
447         connect(m_readnotifier, SIGNAL(activated(int)), this, SLOT(startTask()));
448     }
449
450     void RemotePluginProcess::enableCancelThread()
451     {
452         QEventLoop loop;
453         connect(cancelThread,
454                 SIGNAL(started()),
455                 &loop,
456                 SLOT(quit()));
457
458         m_readnotifier->setEnabled(false);
459         QTimer::singleShot(0.5*1000, &loop, SLOT(quit()));
460         cancelThread->start();
461         loop.exec();
462         QThread::yieldCurrentThread();
463     }
464
465     void RemotePluginProcess::disableCancelThread()
466     {
467         if (!cancelThread->isRunning())
468             return;
469
470         /**
471          * Sort of terrible workaround which
472          * I do not know how to fix: the thread
473          * could hang up during wait up without
474          * these loops and sleeps
475          */
476         cancelThread->quit();
477
478         TRACE() << "Before the isFinished loop ";
479
480         int i = 0;
481         while (!cancelThread->isFinished()) {
482             cancelThread->quit();
483             TRACE() << "Internal iteration " << i++;
484             usleep(0.005 * 1000000);
485         }
486
487         if (!cancelThread->wait(500)) {
488             BLAME() << "Cannot disable cancel thread";
489             int i;
490             for (i = 0; i < 5; i++) {
491                 usleep(0.01 * 1000000);
492                 if (cancelThread->wait(500))
493                     break;
494             }
495
496             if (i == 5) {
497                 BLAME() << "Cannot do anything with cancel thread";
498                 cancelThread->terminate();
499                 cancelThread->wait();
500             }
501         }
502
503         m_readnotifier->setEnabled(true);
504     }
505
506     void RemotePluginProcess::startTask()
507     {
508         quint32 opcode = PLUGIN_OP_STOP;
509         bool is_stopped = false;
510
511         QDataStream in(m_encryptedInDevice);
512         in >> opcode;
513
514         switch (opcode) {
515             case PLUGIN_OP_CANCEL:
516             {
517                 m_plugin->cancel(); break;
518                 //still do not have clear understanding
519                 //of the cancelation-stop mechanism
520                 //is_stopped = true;
521             }
522             break;
523             case PLUGIN_OP_TYPE:
524                 type();
525                 break;
526             case PLUGIN_OP_MECHANISMS:
527                 mechanisms();
528                 break;
529             case PLUGIN_OP_PROCESS:
530                 process();
531                 break;
532             case PLUGIN_OP_PROCESS_UI:
533                 userActionFinished();
534                 break;
535             case PLUGIN_OP_REFRESH:
536                 refresh();
537                 break;
538             case PLUGIN_OP_STOP:
539                 is_stopped = true;
540                 break;
541             default:
542             {
543                 qCritical() << " unknown operation code: " << opcode;
544                 is_stopped = true;
545             }
546             break;
547         };
548
549         TRACE() << "operation is completed";
550
551         if (!is_stopped) {
552             if (!m_outfile.flush())
553                 is_stopped = true;
554         }
555
556         if (is_stopped)
557         {
558             m_plugin->abort();
559             emit processStopped();
560         }
561     }
562
563     CancelEventThread::CancelEventThread(AuthPluginInterface *plugin, EncryptedDevice *encryptedInDevice)
564     {
565         m_plugin = plugin;
566         m_cancelNotifier = 0;
567         m_encryptedInDevice = encryptedInDevice;
568     }
569
570     CancelEventThread::~CancelEventThread()
571     {
572         delete m_cancelNotifier;
573     }
574
575     void CancelEventThread::run()
576     {
577         if (!m_cancelNotifier) {
578             m_cancelNotifier = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read);
579             connect(m_cancelNotifier, SIGNAL(activated(int)), this, SLOT(cancel()), Qt::DirectConnection);
580         }
581
582         m_cancelNotifier->setEnabled(true);
583         exec();
584         m_cancelNotifier->setEnabled(false);
585     }
586
587     void CancelEventThread::cancel()
588     {
589         char buf[4];
590         memset(buf, 0, 4);
591         int n = 0;
592
593         if (!(n = read(STDIN_FILENO, buf, 4))) {
594             qCritical() << "Cannot read from cancel socket";
595             return;
596         }
597
598         /*
599          * Read the actual value of
600          * */
601         QByteArray ba(buf, 4);
602         m_encryptedInDevice->setTemporaryDataSource(&ba);
603         quint32 opcode;
604         QDataStream ds(m_encryptedInDevice);
605         ds >> opcode;
606         m_encryptedInDevice->clearTemporaryDataSource();
607         if (opcode != PLUGIN_OP_CANCEL)
608             qCritical() << "wrong operation code: breakage of remotepluginprocess threads synchronization: " << opcode;
609
610         m_plugin->cancel();
611     }
612 } //namespace RemotePluginProcessNS
613