some local definition of strings were changes to constants
[accounts-sso:vitalyrepins-signon.git] / src / signond / pluginproxy.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
23 #include "pluginproxy.h"
24
25 #include <sys/types.h>
26 #include <pwd.h>
27
28 #include <QStringList>
29 #include <QThreadStorage>
30 #include <QThread>
31
32 #include "SignOn/uisessiondata_priv.h"
33 #include "SignOn/signonplugincommon.h"
34
35 /*
36  *   TODO: remove the "SignOn/authpluginif.h" include below after the removal
37  *         of the deprecated error handling (needed here only for the deprecated
38  *         AuthPluginError::PLUGIN_ERROR_GENERAL).
39  */
40 #include "SignOn/authpluginif.h"
41
42
43 using namespace SignOn;
44
45 //TODO get this from config
46 #define REMOTEPLUGIN_BIN_PATH QLatin1String("/usr/bin/signonpluginprocess")
47 #define PLUGINPROCESS_TIMEOUT 5000
48
49 namespace SignonDaemonNS {
50
51     PluginProcess::PluginProcess(QObject *parent) : QProcess(parent)
52     {
53     }
54
55     PluginProcess::~PluginProcess()
56     {
57         if (state() != QProcess::NotRunning)
58             terminate();
59
60         waitForFinished();
61     }
62
63     void PluginProcess::setupChildProcess()
64     {
65         // Drop all root privileges in the child process and switch to signon user
66 #ifndef NO_SIGNON_USER
67         //get uid and gid
68         struct passwd *passwdRecord = getpwnam("signon");
69         if ( !passwdRecord ){
70             fprintf(stderr, "failed to get user: signon\n");
71             emit QProcess::finished(2, QProcess::NormalExit);
72             exit(2);
73         }
74 #ifdef SIGNOND_TRACE
75         //this is run in remote plugin process, so trace should go to stderr
76         fprintf(stderr, "got user: %s with uid: %d\n", passwdRecord->pw_name, passwdRecord->pw_uid);
77 #endif
78         if (( ::setgid(passwdRecord->pw_gid))
79                 || (::setuid(passwdRecord->pw_uid))
80                 || (::getuid() != passwdRecord->pw_uid)
81                 ) {
82             fprintf(stderr, "failed to set user: %s with uid: %d", passwdRecord->pw_name, passwdRecord->pw_uid);
83             emit QProcess::finished(2, QProcess::NormalExit);
84             exit(2);
85         }
86  #endif
87     }
88
89     PluginProxy::PluginProxy(QString type, QObject *parent)
90             : QObject(parent)
91     {
92         TRACE();
93
94         m_type = type;
95         m_isProcessing = false;
96         m_process = new PluginProcess(this);
97
98         connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(onReadStandardError()));
99
100         /*
101          * TODO: some error handling should be added here, at least remove of current
102          * request data from the top of the queue and reply an error code
103          * */
104         connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onExit(int, QProcess::ExitStatus)));
105         connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError)));
106     }
107
108     PluginProxy::~PluginProxy()
109     {
110     }
111
112     PluginProxy* PluginProxy::createNewPluginProxy(const QString &type)
113     {
114         PluginProxy *pp = new PluginProxy(type);
115
116         QStringList args = QStringList() << pp->m_type;
117         pp->m_process->start(REMOTEPLUGIN_BIN_PATH, args);
118
119         QByteArray tmp;
120
121         if (!pp->waitForStarted(PLUGINPROCESS_TIMEOUT)) {
122             TRACE() << "The process cannot be started";
123             delete pp;
124             return NULL;
125         }
126
127         if (!pp->readOnReady(tmp, PLUGINPROCESS_TIMEOUT)) {
128             TRACE() << "The process cannot load plugin";
129             delete pp;
130             return NULL;
131         }
132
133         pp->m_type = pp->queryType();
134         pp->m_mechanisms = pp->queryMechanisms();
135
136         connect(pp->m_process, SIGNAL(readyReadStandardOutput()), pp, SLOT(onReadStandardOutput()));
137
138         TRACE() << "The process is started";
139         return pp;
140     }
141
142    bool PluginProxy::process(const QString &cancelKey, const QVariantMap &inData, const QString &mechanism)
143    {
144         TRACE();
145         if (!restartIfRequired())
146             return false;
147
148         m_cancelKey = cancelKey;
149         QVariant value = inData.value(SSOUI_KEY_UIPOLICY);
150         m_uiPolicy = value.toInt();
151         //TODO check if this part can be done in SignonSessionCore::startProcess()
152         QVariantMap inDataTmp = inData;
153         if (m_uiPolicy == RequestPasswordPolicy)
154             inDataTmp.remove(SSOUI_KEY_PASSWORD);
155         QDataStream in(m_process);
156
157         in << (quint32)PLUGIN_OP_PROCESS;
158         in << QVariant(inDataTmp);
159         in << QVariant(mechanism);
160
161         m_isProcessing = true;
162
163         return true;
164     }
165
166    bool PluginProxy::processUi(const QString &cancelKey, const QVariantMap &inData)
167    {
168         TRACE() << inData;
169
170         if (!restartIfRequired())
171             return false;
172
173         m_cancelKey = cancelKey;
174
175         QDataStream in(m_process);
176
177         in << (quint32)PLUGIN_OP_PROCESS_UI;
178         in << QVariant(inData);
179
180         m_isProcessing = true;
181
182         return true;
183     }
184
185    bool PluginProxy::processRefresh(const QString &cancelKey, const QVariantMap &inData)
186    {
187         TRACE() << inData;
188
189         if (!restartIfRequired())
190             return false;
191
192         m_cancelKey = cancelKey;
193
194         QDataStream in(m_process);
195
196         in << (quint32)PLUGIN_OP_REFRESH;
197         in << QVariant(inData);
198
199         m_isProcessing = true;
200
201         return true;
202     }
203
204    void PluginProxy::cancel()
205    {
206        TRACE();
207        QDataStream in(m_process);
208        in << (quint32)PLUGIN_OP_CANCEL;
209     }
210
211    void PluginProxy::stop()
212    {
213        TRACE();
214        QDataStream in(m_process);
215        in << (quint32)PLUGIN_OP_STOP;
216     }
217
218     bool PluginProxy::readOnReady(QByteArray &buffer, int timeout)
219     {
220         bool ready = m_process->waitForReadyRead(timeout);
221
222         if (ready) {
223             if (!m_process->bytesAvailable())
224                 return false;
225
226             while (m_process->bytesAvailable())
227                 buffer += m_process->readAllStandardOutput();
228         }
229
230         return ready;
231     }
232
233     bool PluginProxy::isProcessing()
234     {
235         return m_isProcessing;
236     }
237
238     void PluginProxy::onReadStandardOutput()
239     {
240         disconnect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));
241
242         QByteArray token;
243
244         QVariant infoVa;
245         QVariantMap info;
246
247         if (!m_process->bytesAvailable()) {
248             qCritical() << "No information available on process";
249             m_isProcessing = false;
250             emit processError(m_cancelKey, PLUGIN_ERROR_GENERAL, QString());
251             return;
252         }
253
254         QByteArray buffer;
255
256         while (m_process->bytesAvailable())
257             buffer += m_process->readAllStandardOutput();
258
259
260         /*
261          * we need to analyze the whole buffer
262          * and if it contains error then emit only error
263          * otherwise process/emit the incoming information
264          * one by one
265          * */
266         QDataStream out(buffer);
267         bool isResultObtained = false;
268
269         while (out.status() == QDataStream::Ok) {
270             quint32 opres;
271             out >> opres; //result of operation: error code
272
273             TRACE() << opres;
274
275             if (opres == PLUGIN_RESPONSE_RESULT) {
276                 TRACE() << "PLUGIN_RESPONSE_RESULT";
277                 out >> infoVa; //SessionData in QVariant
278                 info = infoVa.toMap();
279                 m_isProcessing = false;
280
281                 if (!isResultObtained)
282                     emit processResultReply(m_cancelKey, info);
283                 else
284                     BLAME() << "Unexpected plugin response: " << info;
285
286                 isResultObtained = true;
287             } else if (opres == PLUGIN_RESPONSE_UI) {
288                 TRACE() << "PLUGIN_RESPONSE_UI";
289                 out >> infoVa; //UiSessionData in QVariant
290                 info = infoVa.toMap();
291
292                 if (!isResultObtained) {
293                     bool allowed = true;
294
295                     if (m_uiPolicy == NoUserInteractionPolicy)
296                         allowed = false;
297
298                     if (m_uiPolicy == ValidationPolicy &&
299                         !info.contains(SSOUI_KEY_CAPTCHAIMG) &&
300                         !info.contains(SSOUI_KEY_CAPTCHAURL))
301                         allowed = false;
302
303                     if (!allowed) {
304                         //set error and return;
305                         TRACE() << "ui policy prevented ui launch";
306                         info.insert(SSOUI_KEY_ERROR, QUERY_ERROR_FORBIDDEN);
307                         processUi(m_cancelKey, info);
308                     } else {
309                         TRACE() << "open ui";
310                         emit processUiRequest(m_cancelKey, info);
311                     }
312                 } else {
313                     BLAME() << "Unexpected plugin ui response: " << info;
314                 }
315             } else if (opres == PLUGIN_RESPONSE_REFRESHED) {
316                 TRACE() << "PLUGIN_RESPONSE_REFRESHED";
317                 out >> infoVa; //UiSessionData in QVariant
318                 info = infoVa.toMap();
319
320                 if (!isResultObtained)
321                     emit processRefreshRequest(m_cancelKey, info);
322                 else
323                     BLAME() << "Unexpected plugin ui response: " << info;
324             } else if (opres == PLUGIN_RESPONSE_ERROR) {
325                 TRACE() << "PLUGIN_RESPONSE_ERROR";
326                 quint32 err;
327                 QString errorMessage;
328                 out >> err;
329                 out >> errorMessage;
330                 m_isProcessing = false;
331
332                 if (!isResultObtained)
333                     emit processError(m_cancelKey, (int)err, errorMessage);
334                 else
335                     BLAME() << "Unexpected plugin error: " << errorMessage;
336
337                 isResultObtained = true;
338             } else if (opres == PLUGIN_RESPONSE_SIGNAL) {
339                 TRACE() << "PLUGIN_RESPONSE_SIGNAL";
340                 quint32 state;
341                 QString message;
342
343                 out >> state;
344                 out >> message;
345
346                 if (!isResultObtained)
347                     emit stateChanged(m_cancelKey, (int)state, message);
348                 else
349                     BLAME() << "Unexpected plugin signal: " << state << " " << message;
350             }
351         }
352
353         connect(m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadStandardOutput()));
354     }
355
356     void PluginProxy::onReadStandardError()
357     {
358         QString ba = QString::fromLatin1(m_process->readAllStandardError());
359         TRACE() << ba;
360     }
361
362     void PluginProxy::onExit(int exitCode, QProcess::ExitStatus exitStatus)
363     {
364         TRACE() << "Plugin process exit with code " << exitCode << " : " << exitStatus;
365
366         if (m_isProcessing || exitStatus == QProcess::CrashExit) {
367             qCritical() << "Challenge produces CRASH!";
368             emit processError(m_cancelKey, PLUGIN_ERROR_GENERAL, QLatin1String("plugin processed crashed"));
369         }
370         if (exitCode == 2) {
371             TRACE() << "plugin process terminated because cannot change user";
372         }
373
374         m_isProcessing = false;
375     }
376
377     void PluginProxy::onError(QProcess::ProcessError err)
378     {
379         TRACE() << "Error: " << err;
380     }
381
382     QString PluginProxy::queryType()
383     {
384         TRACE();
385
386         if (!restartIfRequired())
387             return QString();
388
389         QDataStream ds(m_process);
390         ds << (quint32)PLUGIN_OP_TYPE;
391
392         QByteArray typeBa, buffer;
393         bool result;
394
395         if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
396             QDataStream out(buffer);
397             out >> typeBa;
398         } else
399             qCritical("PluginProxy returned NULL result");
400
401         return QString::fromLatin1(typeBa);
402     }
403
404     QStringList PluginProxy::queryMechanisms()
405     {
406         TRACE();
407
408         if (!restartIfRequired())
409             return QStringList();
410
411         QDataStream in(m_process);
412         in << (quint32)PLUGIN_OP_MECHANISMS;
413
414         QByteArray buffer;
415         QStringList strList;
416         bool result;
417
418         if ((result = readOnReady(buffer, PLUGINPROCESS_TIMEOUT))) {
419             QVariant mechanismsVar;
420             QDataStream out(buffer);
421
422             out >> mechanismsVar;
423             QVariantList varList = mechanismsVar.toList();
424
425             for (int i = 0; i < varList.count(); i++)
426                     strList << varList.at(i).toString();
427
428             TRACE() << strList;
429         } else
430             qCritical("PluginProxy returned NULL result");
431
432         return strList;
433     }
434
435     bool PluginProxy::waitForStarted(int timeout)
436     {
437         return m_process->waitForStarted(timeout);
438     }
439
440     bool PluginProxy::waitForFinished(int timeout)
441     {
442         return m_process->waitForFinished(timeout);
443     }
444
445     bool PluginProxy::restartIfRequired()
446     {
447         if (m_process->state() == QProcess::NotRunning) {
448             TRACE() << "RESTART REQUIRED";
449             m_process->start(REMOTEPLUGIN_BIN_PATH, QStringList(m_type));
450
451             QByteArray tmp;
452             if (!waitForStarted(PLUGINPROCESS_TIMEOUT) || !readOnReady(tmp, PLUGINPROCESS_TIMEOUT))
453                 return false;
454         }
455         return true;
456     }
457
458 } //namespace SignonDaemonNS