~x2go/x2go/x2goclient_master

« back to all changes in this revision

Viewing changes to sshprocess.cpp

  • Committer: Mihai Moldovan
  • Date: 2015-03-04 20:15:47 UTC
  • Revision ID: git-v1:b7398771a7abd84ddcff407063edb95dd0a205d3
general: move *.cpp and *.h files to src/ and *.ts files to src/i18n/.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/**************************************************************************
2
 
*   Copyright (C) 2005-2015 by Oleksandr Shneyder                         *
3
 
*   o.shneyder@phoca-gmbh.de                                              *
4
 
*                                                                         *
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.                          *
13
 
*                                                                         *
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
 
***************************************************************************/
17
 
 
18
 
#include "x2goclientconfig.h"
19
 
#include "x2gologdebug.h"
20
 
#include "sshmasterconnection.h"
21
 
#include "sshprocess.h"
22
 
#include <QTimer>
23
 
#include <QUuid>
24
 
 
25
 
#include <QProcess>
26
 
#ifndef Q_OS_WIN
27
 
#include <arpa/inet.h>
28
 
#include <netinet/tcp.h>
29
 
#endif
30
 
 
31
 
// #undef DEBUG
32
 
#define DEBUG
33
 
 
34
 
#define KEEPALIVE_OPTION " -o ServerAliveInterval=60 "
35
 
 
36
 
SshProcess::SshProcess(SshMasterConnection* master, int pid): QObject(0)
37
 
{
38
 
    masterCon=master;
39
 
    serverSocket=0;
40
 
    connect(master,SIGNAL(stdErr(SshProcess*,QByteArray)),this,SLOT(slotStdErr(SshProcess*,QByteArray)));
41
 
    connect(master,SIGNAL(ioErr(SshProcess*,QString,QString)),this,SLOT(slotIOerr(SshProcess*,QString,QString)));
42
 
    tunnel=false;
43
 
    normalExited=true;
44
 
    this->pid=pid;
45
 
    proc=0l;
46
 
    execProcess=false;
47
 
}
48
 
 
49
 
SshProcess::~SshProcess()
50
 
{
51
 
#ifdef DEBUG
52
 
    x2goDebug<<"ssh process destructor";
53
 
#endif
54
 
 
55
 
    if (proc)
56
 
    {
57
 
        if (tunnel)
58
 
        {
59
 
            disconnect(proc,SIGNAL(finished(int,QProcess::ExitStatus)),this,
60
 
                       SLOT(slotSshProcFinished(int,QProcess::ExitStatus)));
61
 
            disconnect(proc,SIGNAL(readyReadStandardError()),this,SLOT(slotSshProcStdErr()));
62
 
            disconnect(proc,SIGNAL(readyReadStandardOutput()),this,SLOT(slotSshProcStdOut()));
63
 
        }
64
 
        if (proc->state()==QProcess::Running && execProcess)
65
 
        {
66
 
            if(!proc->waitForFinished(3000))
67
 
            {
68
 
                proc->terminate();
69
 
            }
70
 
        }
71
 
        if (proc->state()==QProcess::Running)
72
 
        {
73
 
            proc->kill();
74
 
        }
75
 
        if(proc->state()!=QProcess::Running)
76
 
        {
77
 
            delete proc;
78
 
        }
79
 
        proc=0;
80
 
    }
81
 
    if (serverSocket>0)
82
 
    {
83
 
#ifdef Q_OS_WIN
84
 
        closesocket(serverSocket);
85
 
        WSACleanup();
86
 
 
87
 
#else
88
 
        close(serverSocket);
89
 
#endif
90
 
    }
91
 
}
92
 
 
93
 
 
94
 
void SshProcess::slotCheckNewConnection()
95
 
{
96
 
    fd_set rfds;
97
 
    struct timeval tv;
98
 
 
99
 
    tv.tv_sec = 0;
100
 
    tv.tv_usec = 0;
101
 
 
102
 
    FD_ZERO(&rfds);
103
 
    FD_SET(serverSocket, &rfds);
104
 
 
105
 
    if (select(serverSocket+1,&rfds,NULL,NULL,&tv)<=0)
106
 
        return;
107
 
 
108
 
#ifdef DEBUG
109
 
    x2goDebug<<"new tcp connection\n";
110
 
#endif
111
 
    int tcpSocket=accept(serverSocket, (struct sockaddr*)&address,&addrlen);
112
 
 
113
 
#ifdef DEBUG
114
 
    x2goDebug<<"new socket:"<<tcpSocket<<endl;
115
 
#endif
116
 
    masterCon->addChannelConnection(this, tcpSocket, forwardHost, forwardPort, localHost,
117
 
                                    ntohs(address.sin_port));
118
 
}
119
 
 
120
 
 
121
 
void SshProcess::tunnelLoop()
122
 
{
123
 
 
124
 
    serverSocket=socket(AF_INET, SOCK_STREAM, 0);
125
 
    if (serverSocket<=0)
126
 
    {
127
 
        QString err=tr("Error creating socket");
128
 
        x2goDebug<<err<<endl;
129
 
        emit sshFinished(false,err,pid);
130
 
        return;
131
 
    }
132
 
#ifndef Q_OS_WIN
133
 
    const int y=1;
134
 
#else
135
 
    const char y=1;
136
 
#endif
137
 
    setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR,&y, sizeof(int));
138
 
    setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY,&y, sizeof(int));
139
 
 
140
 
    address.sin_family=AF_INET;
141
 
    address.sin_addr.s_addr=INADDR_ANY;
142
 
    address.sin_port=htons(localPort);
143
 
    if (bind(serverSocket,(struct sockaddr*) &address,sizeof(address))!=0)
144
 
    {
145
 
        QString err=tr("Error binding ")+localHost+":"+QString::number(localPort);
146
 
        x2goDebug<<err<<endl;
147
 
        emit sshFinished(false,err,pid);
148
 
        return;
149
 
    }
150
 
    listen(serverSocket,5);
151
 
    addrlen=sizeof(struct sockaddr_in);
152
 
    QTimer* timer=new QTimer();
153
 
    connect(timer,SIGNAL(timeout()),this,SLOT(slotCheckNewConnection()));
154
 
    timer->start(100);
155
 
    emit sshTunnelOk(pid);
156
 
#ifdef DEBUG
157
 
    x2goDebug<<"Direct tunnel: waiting for connections on "<<localHost<<":"<<localPort<<endl;
158
 
#endif
159
 
}
160
 
 
161
 
#ifdef Q_OS_WIN
162
 
#include <QSettings>
163
 
void SshProcess::addPuttyReg(QString host, QString uuidStr)
164
 
{
165
 
    QSettings st("HKEY_CURRENT_USER\\Software\\SimonTatham\\PuTTY\\Sessions\\"+uuidStr,
166
 
                 QSettings::NativeFormat);
167
 
    st.setValue("HostName", host);
168
 
    st.setValue("GssapiFwd", (uint) 1);
169
 
    st.sync();
170
 
}
171
 
 
172
 
void SshProcess::rmPuttyReg(QString uuidStr)
173
 
{
174
 
    QSettings st("HKEY_CURRENT_USER\\Software\\SimonTatham\\PuTTY\\Sessions",
175
 
                 QSettings::NativeFormat);
176
 
    st.remove(uuidStr);
177
 
    st.sync();
178
 
}
179
 
#endif
180
 
 
181
 
void SshProcess::startNormal(const QString& cmd)
182
 
{
183
 
    QUuid uuid = QUuid::createUuid();
184
 
    QString uuidStr = uuid.toString().mid(1, 36).toLower();
185
 
    execProcess=true;
186
 
 
187
 
//#ifdef DEBUG
188
 
// ONLY UNCOMMENT FOR TESTING, MIGHT REVEAL PASSWORD WHEN command=RDP
189
 
    x2goDebug<<"executing remote command via SshProcess object ("<<pid<<"): "<<cmd<<endl;
190
 
// #endif
191
 
    if(!masterCon->useKerberos())
192
 
    {
193
 
        QString shcmd = "sh -c 'echo X2GODATABEGIN:" + uuidStr + "; PATH=/usr/local/bin:/usr/bin:/bin "+cmd+"; echo X2GODATAEND:" + uuidStr +";'";
194
 
        masterCon->addChannelConnection(this, uuidStr, shcmd);
195
 
        connect(masterCon,SIGNAL(stdOut(SshProcess*,QByteArray)),this,SLOT(slotStdOut(SshProcess*,QByteArray)));
196
 
        connect(masterCon,SIGNAL(channelClosed(SshProcess*,QString)), this,SLOT(slotChannelClosed(SshProcess*,QString)));
197
 
    }
198
 
    else
199
 
    {
200
 
        QString host=masterCon->getHost();
201
 
        QString shcmd = "sh -c 'echo X2GODATABEGIN:" + uuidStr + "; PATH=/usr/local/bin:/usr/bin:/bin "+cmd+"; echo X2GODATAEND:" + uuidStr +";'";
202
 
        proc=new QProcess(this);
203
 
#ifdef Q_OS_WIN
204
 
        if(masterCon->get_kerberosDelegation())
205
 
        {
206
 
            addPuttyReg(host, uuidStr);
207
 
            host = uuidStr;
208
 
        }
209
 
        QString sshString="plink -batch -P "+
210
 
#else
211
 
        QString krbDelegOption=" -k ";
212
 
        if(masterCon->get_kerberosDelegation())
213
 
        {
214
 
            krbDelegOption=" -K ";
215
 
        }
216
 
        QString sshString=QString::null+"ssh"+ KEEPALIVE_OPTION +krbDelegOption+" -o GSSApiAuthentication=yes -o PasswordAuthentication=no -o PubkeyAuthentication=no -p "+
217
 
#endif
218
 
                          QString::number(masterCon->getPort())+" -l "+
219
 
                          masterCon->getUser()+" "+ host +  " \""+shcmd+"\"";
220
 
 
221
 
        x2goDebug<<"evoking ssh command via SshProcess object ("<<pid<<"): "<<sshString<<endl;
222
 
        procUuid=uuidStr;
223
 
        proc->start(sshString);
224
 
 
225
 
        if (!proc->waitForStarted(15000))
226
 
        {
227
 
            stdErrString=proc->errorString();
228
 
#ifdef DEBUG
229
 
            //x2goDebug<<"ssh start failed:" <<stdErrString<<endl;
230
 
#endif
231
 
            slotChannelClosed(this, uuidStr);
232
 
            return;
233
 
        }
234
 
        connect(proc,SIGNAL(finished(int,QProcess::ExitStatus)),this,
235
 
                SLOT(slotSshProcFinished(int,QProcess::ExitStatus)));
236
 
        connect(proc,SIGNAL(readyReadStandardError()),this,SLOT(slotSshProcStdErr()));
237
 
        connect(proc,SIGNAL(readyReadStandardOutput()),this,SLOT(slotSshProcStdOut()));
238
 
    }
239
 
 
240
 
}
241
 
 
242
 
void SshProcess::start_cp(QString src, QString dst)
243
 
{
244
 
    x2goDebug<<"copying file via SshProcess object ("<<pid<<"): "<<src<<" -> "<<dst<<endl;
245
 
 
246
 
    scpSource=src;
247
 
    if(!masterCon->useKerberos())
248
 
    {
249
 
        connect(masterCon, SIGNAL(copyErr(SshProcess*,QString,QString)), this,
250
 
                SLOT(slotCopyErr(SshProcess*,QString,QString)));
251
 
        connect(masterCon, SIGNAL(copyOk(SshProcess*)), this,SLOT(slotCopyOk(SshProcess*)));
252
 
        masterCon->addCopyRequest(this,src,dst);
253
 
    }
254
 
    else
255
 
    {
256
 
        proc=new QProcess(this);
257
 
#ifdef Q_OS_WIN
258
 
//pscp don't working with paths like "~user"
259
 
//I hope a home directories of your users are in /home/
260
 
        dst.replace("~"+masterCon->getUser(),"/home/"+masterCon->getUser());
261
 
        dst.replace("~","/home/"+masterCon->getUser());
262
 
 
263
 
        QString sshString="pscp -batch -P "+
264
 
#else
265
 
        QString sshString="scp -o GSSApiAuthentication=yes -o PasswordAuthentication=no -o PubkeyAuthentication=no -P "+
266
 
#endif
267
 
                          QString::number(masterCon->getPort())+" "+src+" "+
268
 
                          masterCon->getUser()+"@"+ masterCon->getHost()+":"+dst;
269
 
#ifdef DEBUG
270
 
        x2goDebug<<"running scp:" <<sshString<<endl;
271
 
#endif
272
 
        proc->start(sshString);
273
 
 
274
 
        if (!proc->waitForStarted(15000))
275
 
        {
276
 
            stdErrString=proc->errorString();
277
 
#ifdef DEBUG
278
 
            x2goDebug<<"ssh start failed:" <<stdErrString<<endl;
279
 
#endif
280
 
            slotChannelClosed(this,"");
281
 
            return;
282
 
        }
283
 
        connect(proc,SIGNAL(finished(int,QProcess::ExitStatus)),this,
284
 
                SLOT(slotSshProcFinished(int,QProcess::ExitStatus)));
285
 
        connect(proc,SIGNAL(readyReadStandardError()),this,SLOT(slotSshProcStdErr()));
286
 
        connect(proc,SIGNAL(readyReadStandardOutput()),this,SLOT(slotSshProcStdOut()));
287
 
    }
288
 
}
289
 
 
290
 
 
291
 
void SshProcess::startTunnel(const QString& forwardHost, uint forwardPort, const QString& localHost,
292
 
                             uint localPort, bool reverse)
293
 
{
294
 
    x2goDebug<<"Starting tunnel via SshProcess object ("<<pid<<"): "<<forwardHost<<":"<<forwardPort<<" -> "<<localHost<<":"<<localPort<<endl;
295
 
 
296
 
    tunnel=true;
297
 
    tunnelOkEmited=false;
298
 
    if(!masterCon->useKerberos())
299
 
    {
300
 
        this->forwardHost=forwardHost;
301
 
        this->forwardPort=forwardPort;
302
 
        this->localHost=localHost;
303
 
        this->localPort=localPort;
304
 
        if (!reverse)
305
 
            tunnelLoop();
306
 
    }
307
 
    else
308
 
    {
309
 
        proc=new QProcess(0);
310
 
#ifdef Q_OS_WIN
311
 
        QString sshString="plink -batch -P "+
312
 
#else
313
 
        QString sshString=QString::null+"ssh"+ KEEPALIVE_OPTION +"-o GSSApiAuthentication=yes -o PasswordAuthentication=no -o PubkeyAuthentication=no -p "+
314
 
#endif
315
 
                          QString::number(masterCon->getPort())+" "+
316
 
                          masterCon->getUser()+"@"+
317
 
                          masterCon->getHost() + " -N -v ";
318
 
        if (!reverse)
319
 
            sshString+=" -L " + QString::number(localPort)+":"+forwardHost+":"+QString::number(forwardPort);
320
 
        else
321
 
            sshString+=" -R "+ QString::number(forwardPort)+":"+forwardHost+":"+QString::number(localPort);
322
 
 
323
 
#ifdef DEBUG
324
 
        x2goDebug<<"TUNNEL: running ssh:" <<sshString<<endl;
325
 
#endif
326
 
        proc->start(sshString);
327
 
 
328
 
        if (!proc->waitForStarted(5000))
329
 
        {
330
 
            stdErrString=proc->errorString();
331
 
#ifdef DEBUG
332
 
            x2goDebug<<"ssh start failed:" <<stdErrString<<endl;
333
 
#endif
334
 
            slotChannelClosed(this,"");
335
 
            return;
336
 
        }
337
 
        connect(proc,SIGNAL(finished(int,QProcess::ExitStatus)),this,
338
 
                SLOT(slotSshProcFinished(int,QProcess::ExitStatus)));
339
 
        connect(proc,SIGNAL(readyReadStandardError()),this,SLOT(slotSshProcStdErr()));
340
 
        connect(proc,SIGNAL(readyReadStandardOutput()),this,SLOT(slotSshProcStdOut()));
341
 
    }
342
 
}
343
 
 
344
 
void SshProcess::slotStdErr(SshProcess* creator, QByteArray data)
345
 
{
346
 
    if (creator!=this)
347
 
        return;
348
 
#ifdef DEBUG
349
 
//     x2goDebug<<"new err data:"<<data<<endl;
350
 
#endif
351
 
    stdErrString+=data;
352
 
 
353
 
    if(tunnel && !tunnelOkEmited)
354
 
    {
355
 
#ifdef Q_OS_WIN
356
 
        if(stdErrString.indexOf("Access granted")!=-1)
357
 
#else
358
 
        if(stdErrString.indexOf("Entering interactive session")!=-1)
359
 
#endif
360
 
        {
361
 
            tunnelOkEmited=true;
362
 
#ifdef DEBUG
363
 
            x2goDebug<<"Tunnel OK"<<endl;
364
 
#endif
365
 
            emit sshTunnelOk(pid);
366
 
        }
367
 
    }
368
 
}
369
 
 
370
 
void SshProcess::slotStdOut(SshProcess* creator, QByteArray data)
371
 
{
372
 
    if (creator!=this)
373
 
        return;
374
 
//     x2goDebug<<"new data"<<data<<endl;
375
 
    stdOutString+=data;
376
 
}
377
 
 
378
 
void SshProcess::slotIOerr(SshProcess* creator, QString message, QString sshSessionErr)
379
 
{
380
 
    if (creator!=this)
381
 
        return;
382
 
    if (sshSessionErr.length())
383
 
        sshSessionErr = " - "+sshSessionErr;
384
 
#ifdef DEBUG
385
 
    x2goDebug<<"I/O error: "<<message<<sshSessionErr<<" ("<<pid<<")."<<endl;
386
 
#endif
387
 
    normalExited=false;
388
 
    abortString="I/O error: "+message+sshSessionErr;
389
 
}
390
 
 
391
 
void SshProcess::slotCopyErr(SshProcess* creator, QString message, QString sshSessionErr)
392
 
{
393
 
    if (creator!=this)
394
 
        return;
395
 
    emit sshFinished(false, message+" - "+sshSessionErr, pid);
396
 
}
397
 
 
398
 
void SshProcess::slotCopyOk(SshProcess* creator)
399
 
{
400
 
    if (creator!=this)
401
 
        return;
402
 
    emit sshFinished(true,"", pid);
403
 
}
404
 
 
405
 
void SshProcess::slotReverseTunnelOk(SshProcess* creator)
406
 
{
407
 
    if (creator==this)
408
 
        emit sshTunnelOk(pid);
409
 
}
410
 
 
411
 
void SshProcess::slotReverseTunnelFailed(SshProcess* creator, QString error)
412
 
{
413
 
    if (creator==this)
414
 
        emit sshFinished(false,error,pid);
415
 
 
416
 
}
417
 
 
418
 
 
419
 
void SshProcess::slotChannelClosed(SshProcess* creator, QString uuid)
420
 
{
421
 
    if (creator!=this)
422
 
        return;
423
 
    QString output;
424
 
    if (!normalExited)
425
 
    {
426
 
        output=abortString;
427
 
        if (output.length()<5)
428
 
        {
429
 
            output=stdErrString;
430
 
        }
431
 
    }
432
 
    else
433
 
    {
434
 
        QString begin_marker = "X2GODATABEGIN:"+uuid+"\n";
435
 
        QString end_marker = "X2GODATAEND:"+uuid+"\n";
436
 
        int output_begin=stdOutString.indexOf(begin_marker) + begin_marker.length();
437
 
        int output_end=stdOutString.indexOf(end_marker);
438
 
        output = stdOutString.mid(output_begin, output_end-output_begin);
439
 
        if ( output.length()<=0 &&  stdErrString.length() >0 )
440
 
        {
441
 
            normalExited=false;
442
 
            output=stdErrString;
443
 
#ifdef DEBUG
444
 
            x2goDebug<<"have only stderr, something must be wrong"<<endl;
445
 
#endif
446
 
        }
447
 
    }
448
 
#ifdef DEBUG
449
 
    x2goDebug<<"ssh finished: "<<normalExited<<" - "<<output<<" ("<<pid<<")."<<endl;
450
 
#endif
451
 
    emit sshFinished(normalExited, output, pid);
452
 
}
453
 
 
454
 
void SshProcess::slotSshProcFinished(int exitCode, QProcess::ExitStatus exitStatus)
455
 
{
456
 
    normalExited=false;
457
 
    if (exitCode==0 && exitStatus==QProcess::NormalExit)
458
 
        normalExited=true;
459
 
#ifdef DEBUG
460
 
    x2goDebug<<"ssh process exit code :"<<exitStatus;
461
 
#endif
462
 
#ifdef Q_OS_WIN
463
 
    if(masterCon->useKerberos())
464
 
    {
465
 
        rmPuttyReg(procUuid);
466
 
    }
467
 
#endif
468
 
    slotChannelClosed(this,procUuid);
469
 
}
470
 
 
471
 
void SshProcess::slotSshProcStdErr()
472
 
{
473
 
    slotStdErr(this, proc->readAllStandardError());
474
 
}
475
 
 
476
 
void SshProcess::slotSshProcStdOut()
477
 
{
478
 
    slotStdOut(this, proc->readAllStandardOutput());
479
 
}