1
/**************************************************************************
2
* Copyright (C) 2005-2015 by Oleksandr Shneyder *
3
* o.shneyder@phoca-gmbh.de *
5
* This program is free software; you can redistribute it and/or modify *
6
* it under the terms of the GNU General Public License as published by *
7
* the Free Software Foundation; either version 2 of the License, or *
8
* (at your option) any later version. *
9
* This program is distributed in the hope that it will be useful, *
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12
* GNU General Public License for more details. *
14
* You should have received a copy of the GNU General Public License *
15
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
16
***************************************************************************/
18
#include "httpbrokerclient.h"
19
#include <QNetworkAccessManager>
21
#include <QNetworkRequest>
22
#include <QNetworkReply>
24
#include <QTextStream>
28
#include "x2gologdebug.h"
29
#include <QMessageBox>
31
#include "onmainwindow.h"
32
#include "x2gosettings.h"
33
#include <QDesktopWidget>
36
#include "onmainwindow.h"
37
#include <QTemporaryFile>
38
#include <QInputDialog>
41
HttpBrokerClient::HttpBrokerClient ( ONMainWindow* wnd, ConfigFile* cfg )
46
QUrl lurl ( config->brokerurl );
47
if(lurl.userName().length()>0)
48
config->brokerUser=lurl.userName();
49
nextAuthId=config->brokerUserId;
51
if(config->brokerurl.indexOf("ssh://")==0)
54
x2goDebug<<"host:"<<lurl.host();
55
x2goDebug<<"port:"<<lurl.port(22);
56
x2goDebug<<"uname:"<<lurl.userName();
57
x2goDebug<<"path:"<<lurl.path();
58
config->sshBrokerBin=lurl.path();
64
if ((config->brokerCaCertFile.length() >0) && (QFile::exists(config->brokerCaCertFile))) {
65
QSslSocket::addDefaultCaCertificates(config->brokerCaCertFile, QSsl::Pem);
66
x2goDebug<<"Custom CA certificate file loaded into HTTPS broker client: "<<config->brokerCaCertFile;
69
http=new QNetworkAccessManager ( this );
70
x2goDebug<<"Setting up connection to broker: "<<config->brokerurl;
72
connect ( http, SIGNAL ( sslErrors ( QNetworkReply*, const QList<QSslError>& ) ),this,
73
SLOT ( slotSslErrors ( QNetworkReply*, const QList<QSslError>& ) ) );
75
connect ( http,SIGNAL ( finished (QNetworkReply*) ),this,
76
SLOT ( slotRequestFinished (QNetworkReply*) ) );
81
HttpBrokerClient::~HttpBrokerClient()
85
void HttpBrokerClient::createSshConnection()
87
QUrl lurl ( config->brokerurl );
88
sshConnection=new SshMasterConnection (this, lurl.host(), lurl.port(22),false,
89
config->brokerUser, config->brokerPass,config->brokerSshKey,config->brokerAutologin,
90
config->brokerKrbLogin, false);
92
connect ( sshConnection, SIGNAL ( connectionOk(QString)), this, SLOT ( slotSshConnectionOk() ) );
93
connect ( sshConnection, SIGNAL ( serverAuthError ( int,QString, SshMasterConnection* ) ),this,
94
SLOT ( slotSshServerAuthError ( int,QString, SshMasterConnection* ) ) );
95
connect ( sshConnection, SIGNAL ( needPassPhrase(SshMasterConnection*, bool)),this,
96
SLOT ( slotSshServerAuthPassphrase(SshMasterConnection*, bool)) );
97
connect ( sshConnection, SIGNAL ( userAuthError ( QString ) ),this,SLOT ( slotSshUserAuthError ( QString ) ) );
98
connect ( sshConnection, SIGNAL ( connectionError(QString,QString)), this,
99
SLOT ( slotSshConnectionError ( QString,QString ) ) );
100
sshConnection->start();
103
void HttpBrokerClient::slotSshConnectionError(QString message, QString lastSessionError)
107
sshConnection->wait();
108
delete sshConnection;
112
QMessageBox::critical ( 0l,message,lastSessionError,
114
QMessageBox::NoButton );
117
void HttpBrokerClient::slotSshConnectionOk()
122
void HttpBrokerClient::slotSshServerAuthError(int error, QString sshMessage, SshMasterConnection* connection)
127
case SSH_SERVER_KNOWN_CHANGED:
128
errMsg=tr ( "Host key for server changed.\nIt is now: " ) +sshMessage+"\n"+
129
tr ( "For security reasons, connection will be stopped" );
130
connection->writeKnownHosts(false);
132
if(sshConnection && sshConnection !=connection)
134
sshConnection->wait();
135
delete sshConnection;
138
slotSshUserAuthError ( errMsg );
141
case SSH_SERVER_FOUND_OTHER:
142
errMsg=tr ( "The host key for this server was not found but an other"
143
"type of key exists.An attacker might change the default server key to"
144
"confuse your client into thinking the key does not exist" );
145
connection->writeKnownHosts(false);
147
if(sshConnection && sshConnection !=connection)
149
sshConnection->wait();
150
delete sshConnection;
153
slotSshUserAuthError ( errMsg );
156
case SSH_SERVER_ERROR:
157
connection->writeKnownHosts(false);
159
if(sshConnection && sshConnection !=connection)
161
sshConnection->wait();
162
delete sshConnection;
165
slotSshUserAuthError ( sshMessage );
167
case SSH_SERVER_FILE_NOT_FOUND:
168
errMsg=tr ( "Could not find known host file."
169
"If you accept the host key here, the file will be automatically created" );
172
case SSH_SERVER_NOT_KNOWN:
173
errMsg=tr ( "The server is unknown. Do you trust the host key?\nPublic key hash: " ) +sshMessage;
177
if ( QMessageBox::warning ( 0, tr ( "Host key verification failed" ),errMsg,tr ( "Yes" ), tr ( "No" ) ) !=0 )
179
connection->writeKnownHosts(false);
181
if(sshConnection && sshConnection !=connection)
183
sshConnection->wait();
184
delete sshConnection;
187
slotSshUserAuthError ( tr ( "Host key verification failed" ) );
190
connection->writeKnownHosts(true);
196
void HttpBrokerClient::slotSshServerAuthPassphrase(SshMasterConnection* connection, bool verificationCode)
203
message=tr("Verification code:");
207
message=tr("Enter passphrase to decrypt a key");
211
QString phrase=QInputDialog::getText(0,connection->getUser()+"@"+connection->getHost()+":"+QString::number(connection->getPort()),
212
message, QLineEdit::Password,QString::null, &ok);
215
phrase=QString::null;
219
if(phrase==QString::null)
222
connection->setKeyPhrase(phrase);
226
void HttpBrokerClient::slotSshUserAuthError(QString error)
230
sshConnection->wait();
231
delete sshConnection;
235
QMessageBox::critical ( 0l,tr ( "Authentication failed" ),error,
237
QMessageBox::NoButton );
242
void HttpBrokerClient::getUserSessions()
244
QString brokerUser=config->brokerUser;
245
// Otherwise, after logout from the session, we will be connected by a previous user without a password by authid.
246
if (config->brokerAutologoff) {
247
nextAuthId=config->brokerUserId;
249
x2goDebug<<"called getUserSessions: brokeruser: "<<brokerUser<<" authid: "<<nextAuthId;
250
if(mainWindow->getUsePGPCard())
251
brokerUser=mainWindow->getCardLogin();
252
config->sessiondata=QString::null;
256
QTextStream ( &req ) <<
257
"task=listsessions&"<<
258
"user="<<QUrl::toPercentEncoding(brokerUser)<<"&"<<
259
"password="<<QUrl::toPercentEncoding(config->brokerPass)<<"&"<<
260
"authid="<<nextAuthId;
262
x2goDebug << "sending request: "<< req.toUtf8();
263
QNetworkRequest request(QUrl(config->brokerurl));
264
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
265
sessionsRequest=http->post (request, req.toUtf8() );
271
createSshConnection();
274
if (nextAuthId.length() > 0) {
275
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --authid "+nextAuthId+ " --task listsessions",
276
this, SLOT ( slotListSessions ( bool, QString,int ) ));
278
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --task listsessions",
279
this, SLOT ( slotListSessions ( bool, QString,int ) ));
284
void HttpBrokerClient::selectUserSession(const QString& session)
286
x2goDebug<<"Called selectUserSession for session "<<session<<".";
287
QString brokerUser=config->brokerUser;
288
if(mainWindow->getUsePGPCard())
289
brokerUser=mainWindow->getCardLogin();
294
QTextStream ( &req ) <<
295
"task=selectsession&"<<
296
"sid="<<session<<"&"<<
297
"user="<<QUrl::toPercentEncoding(brokerUser)<<"&"<<
298
"password="<<QUrl::toPercentEncoding(config->brokerPass)<<"&"<<
299
"authid="<<nextAuthId;
300
x2goDebug << "sending request: "<< req.toUtf8();
301
QNetworkRequest request(QUrl(config->brokerurl));
302
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
303
selSessRequest=http->post (request, req.toUtf8() );
308
if (nextAuthId.length() > 0) {
309
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --authid "+nextAuthId+ " --task selectsession --sid \""+session+"\"",
310
this,SLOT ( slotSelectSession(bool,QString,int)));
312
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --task selectsession --sid \""+session+"\"",
313
this,SLOT ( slotSelectSession(bool,QString,int)));
319
void HttpBrokerClient::changePassword(QString newPass)
321
newBrokerPass=newPass;
322
QString brokerUser=config->brokerUser;
323
if(mainWindow->getUsePGPCard())
324
brokerUser=mainWindow->getCardLogin();
329
QTextStream ( &req ) <<
331
"newpass="<<QUrl::toPercentEncoding(newPass)<<"&"<<
332
"user="<<QUrl::toPercentEncoding(brokerUser)<<"&"<<
333
"password="<<QUrl::toPercentEncoding(config->brokerPass)<<"&"<<
334
"authid="<<nextAuthId;
335
x2goDebug << "sending request: "<< req.toUtf8();
336
QNetworkRequest request(QUrl(config->brokerurl));
337
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
338
chPassRequest=http->post (request, req.toUtf8() );
342
if (nextAuthId.length() > 0) {
343
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --authid "+nextAuthId+ " --task setpass --newpass "+newPass, this,
344
SLOT ( slotPassChanged(bool,QString,int)));
346
sshConnection->executeCommand ( config->sshBrokerBin+" --user "+ brokerUser +" --task setpass --newpass "+newPass, this,
347
SLOT ( slotPassChanged(bool,QString,int)));
352
void HttpBrokerClient::testConnection()
354
x2goDebug<<"called testConnection";
358
QTextStream ( &req ) <<
360
x2goDebug << "sending request: "<< req.toUtf8();
361
QNetworkRequest request(QUrl(config->brokerurl));
362
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
363
testConRequest=http->post (request, req.toUtf8() );
367
if (nextAuthId.length() > 0) {
368
sshConnection->executeCommand(config->sshBrokerBin+" --authid "+nextAuthId+ " --task testcon",
369
this, SLOT ( slotSelectSession(bool,QString,int)));
371
sshConnection->executeCommand(config->sshBrokerBin+" --task testcon",
372
this, SLOT ( slotSelectSession(bool,QString,int)));
378
void HttpBrokerClient::createIniFile(const QString& raw_content)
381
content = raw_content;
382
content.replace("<br>","\n");
383
x2goDebug<<"inifile content: "<<content<<"\n";
385
QStringList lines=content.split("START_USER_SESSIONS\n");
389
cont=cont.split("END_USER_SESSIONS\n")[0];
391
mainWindow->config.iniFile=cont;
395
bool HttpBrokerClient::checkAccess(QString answer )
397
x2goDebug<<"called checkAccess - answer was: "<<answer;
398
if (answer.indexOf("Access granted")==-1)
400
QMessageBox::critical (
402
tr ( "Login failed!<br>"
403
"Please try again" ) );
407
config->brokerAuthenticated=true;
408
int authBegin=answer.indexOf("AUTHID:");
411
nextAuthId=answer.mid(authBegin+7, answer.indexOf("\n",authBegin)-authBegin-7);
417
void HttpBrokerClient::slotConnectionTest(bool success, QString answer, int)
419
x2goDebug<<"called slotConnectionTest";
423
QMessageBox::critical(0,tr("Error"),answer);
424
emit fatalHttpError();
427
if(!checkAccess(answer))
431
x2goDebug<<"elapsed: "<<requestTime.elapsed()<<"received:"<<answer.size()<<endl;
432
emit connectionTime(requestTime.elapsed(),answer.size());
438
void HttpBrokerClient::slotListSessions(bool success, QString answer, int)
443
QMessageBox::critical(0,tr("Error"),answer);
444
emit fatalHttpError();
447
if(!checkAccess(answer))
449
createIniFile(answer);
450
emit sessionsLoaded();
453
void HttpBrokerClient::slotPassChanged(bool success, QString answer, int)
458
QMessageBox::critical(0,tr("Error"),answer);
459
emit fatalHttpError();
462
if(!checkAccess(answer))
467
void HttpBrokerClient::slotSelectSession(bool success, QString answer, int)
472
QMessageBox::critical(0,tr("Error"),answer);
473
emit fatalHttpError();
476
if(!checkAccess(answer))
478
x2goDebug<<"parsing "<<answer;
479
parseSession(answer);
483
void HttpBrokerClient::slotRequestFinished ( QNetworkReply* reply )
485
if(reply->error() != QNetworkReply::NoError)
487
x2goDebug<<"Broker HTTP request failed with error: "<<reply->errorString();
488
QMessageBox::critical(0,tr("Error"),reply->errorString());
489
emit fatalHttpError();
493
QString answer ( reply->readAll() );
494
x2goDebug<<"A http request returned. Result was: "<<answer;
495
if (reply == testConRequest)
497
slotConnectionTest(true,answer,0);
499
if (reply == sessionsRequest)
501
slotListSessions(true, answer,0);
503
if (reply == selSessRequest)
505
slotSelectSession(true,answer,0);
507
if (reply == chPassRequest)
509
slotPassChanged(true,answer,0);
512
// We receive ownership of the reply object
513
// and therefore need to handle deletion.
514
reply->deleteLater();
517
void HttpBrokerClient::parseSession(QString sinfo)
519
x2goDebug<<"starting parser\n";
520
QStringList lst=sinfo.split("SERVER:",QString::SkipEmptyParts);
521
int keyStartPos=sinfo.indexOf("-----BEGIN DSA PRIVATE KEY-----");
523
keyStartPos=sinfo.indexOf("-----BEGIN RSA PRIVATE KEY-----");
524
QString endStr="-----END DSA PRIVATE KEY-----";
525
int keyEndPos=sinfo.indexOf(endStr);
528
endStr="-----END RSA PRIVATE KEY-----";
529
keyEndPos=sinfo.indexOf(endStr);
531
if (! (keyEndPos == -1 || keyStartPos == -1 || lst.size()==0))
532
config->key=sinfo.mid(keyStartPos, keyEndPos+endStr.length()-keyStartPos);
533
QString serverLine=(lst[1].split("\n"))[0];
534
QStringList words=serverLine.split(":",QString::SkipEmptyParts);
535
config->serverIp=words[0];
537
config->sshport=words[1];
538
x2goDebug<<"server IP: "<<config->serverIp<<"\n";
539
x2goDebug<<"server port: "<<config->sshport<<"\n";
540
if (sinfo.indexOf("SESSION_INFO")!=-1)
542
QStringList lst=sinfo.split("SESSION_INFO:",QString::SkipEmptyParts);
543
config->sessiondata=lst[1];
544
x2goDebug<<"session data: "<<config->sessiondata<<"\n";
546
x2goDebug<<"parsing has finished\n";
547
emit sessionSelected();
551
void HttpBrokerClient::slotSslErrors ( QNetworkReply* netReply, const QList<QSslError> & errors )
554
QSslCertificate cert;
555
for ( int i=0; i<errors.count(); ++i )
557
x2goDebug<<"sslError, code:"<<errors[i].error() <<":";
558
err<<errors[i].errorString();
559
if ( !errors[i].certificate().isNull() )
560
cert=errors[i].certificate();
564
QString md5=getHexVal ( cert.digest() );
566
fname=fname.replace(":","_");
567
QUrl lurl ( config->brokerurl );
568
QString homeDir=mainWindow->getHomeDirectory();
569
if ( QFile::exists ( homeDir+"/.x2go/ssl/exceptions/"+
570
lurl.host() +"/"+fname ) )
572
QFile fl ( homeDir+"/.x2go/ssl/exceptions/"+
573
lurl.host() +"/"+fname );
574
fl.open ( QIODevice::ReadOnly | QIODevice::Text );
575
QSslCertificate mcert ( &fl );
578
netReply->ignoreSslErrors();
579
requestTime.restart();
584
QString text=tr ( "<br><b>Server uses an invalid "
585
"security certificate.</b><br><br>" );
586
text+=err.join ( "<br>" );
587
text+=tr ( "<p style='background:#FFFFDC;'>"
588
"You should not add an exception "
589
"if you are using an internet connection "
590
"that you do not trust completely or if you are "
591
"not used to seeing a warning for this server.</p>" );
592
QMessageBox mb ( QMessageBox::Warning,tr ( "Secure connection failed" ),
595
QTextStream ( &text ) <<err.join ( "\n" ) <<"\n"<<
597
tr ( "Issued to:\n" ) <<
598
tr ( "Common Name(CN)\t" ) <<
599
cert.issuerInfo ( QSslCertificate::CommonName )
601
tr ( "Organization(O)\t" ) <<
602
cert.issuerInfo ( QSslCertificate::Organization )
604
tr ( "Organizational Unit(OU)\t" ) <<
605
cert.issuerInfo ( QSslCertificate::OrganizationalUnitName )
607
tr ( "Serial Number\t" ) <<getHexVal ( cert.serialNumber() )
609
tr ( "Issued by:\n" ) <<
610
tr ( "Common Name(CN)\t" ) <<
611
cert.subjectInfo ( QSslCertificate::CommonName )
613
tr ( "Organization(O)\t" ) <<
614
cert.subjectInfo ( QSslCertificate::Organization )
616
tr ( "Organizational Unit(OU)\t" ) <<
617
cert.subjectInfo ( QSslCertificate::OrganizationalUnitName )
620
tr ( "Validity:\n" ) <<
621
tr ( "Issued on\t" ) <<cert.effectiveDate().toString() <<endl<<
622
tr ( "expires on\t" ) <<cert.expiryDate().toString() <<endl<<endl<<
623
tr ( "Fingerprints:\n" ) <<
625
getHexVal ( cert.digest ( QCryptographicHash::Sha1 ) ) <<endl<<
626
tr ( "MD5\t" ) <<md5;
630
mb.setDetailedText ( text );
632
( QAbstractButton* ) mb.addButton ( tr ( "Exit X2Go Client" ),
633
QMessageBox::RejectRole ) );
634
QPushButton *okButton=mb.addButton ( tr ( "Add exception" ),
635
QMessageBox::AcceptRole );
636
mb.setDefaultButton ( okButton );
639
if ( mb.clickedButton() == ( QAbstractButton* ) okButton )
641
x2goDebug<<"accept certificate";
643
dr.mkpath ( homeDir+"/.x2go/ssl/exceptions/"+lurl.host() +"/" );
644
QFile fl ( homeDir+"/.x2go/ssl/exceptions/"+
645
lurl.host() +"/"+fname );
646
fl.open ( QIODevice::WriteOnly | QIODevice::Text );
647
QTextStream ( &fl ) <<cert.toPem();
649
netReply->ignoreSslErrors();
650
x2goDebug<<"store certificate in "<<homeDir+"/.x2go/ssl/exceptions/"+
651
lurl.host() +"/"+fname;
652
requestTime.restart();
655
emit fatalHttpError();
659
QString HttpBrokerClient::getHexVal ( const QByteArray& ba )
662
for ( int i=0; i<ba.size(); ++i )
665
bt.sprintf ( "%02X", ( unsigned char ) ba[i] );
668
return val.join ( ":" );