1 /***************************************************************************
2 ksshprocess.cpp - description
4 begin : Tue Jul 31 2001
5 copyright : (C) 2001 by Lucas Fisher
6 email : ljfisher@purdue.edu
7 ***************************************************************************/
9 /***************************************************************************
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
16 ***************************************************************************/
19 * See the KSshProcess header for examples on use.
21 * This class uses a hacked version of the PTYProcess
22 * class. This was needed because the kdelibs PTYProcess does not provide
23 * access to the pty file descriptor which we need, because ssh prints the
24 * password prompt to the pty and reads the password from the pty. I don't
25 * feel I know enough about ptys to confidently modify the orignial
28 * To start ssh we take the arguments the user gave us
29 * in the SshOptList and build the ssh command arguments based on the version
30 * of ssh we are using. This command and its arguments are passed to
31 * PTYProcess for execution. Once ssh is started we scan each line of input
32 * from stdin, stderr, and the pty for recognizable strings. The recognizable
33 * strings are taken from several string tables. Each table contains a string
34 * for each specific version of ssh we support and a string for a generic
35 * version of OpenSSH and commercial SSH incase we don't recognized the
36 * specific ssh version strings (as when a new SSH version is released after
37 * a release of KSshProcess). There are tables for ssh version strings,
38 * password prompts, new host key errors, different host key errors,
39 * messages than indicate a successful connect, authentication errors, etc.
40 * If we find user interaction is necessary, for instance to provide a
41 * password or passphrase, we return a err code to the user who can send
42 * a message to KSshProcess, using one of several methods, to correct
45 * Determining when the ssh connection has successfully authenticationed has
46 * proved to be the most difficult challenge. OpenSSH does not print a message
47 * on successful authentication, thus the only way to know is to send data
48 * and wait for a return. The problem here is sometimes it can take a bit
49 * to establish the connection (for example, do to DNS lookups). This means
50 * the user may be sitting there waiting for a connection that failed.
51 * Instead, ssh is always started with the verbose flag. Then we look for
52 * a message that indicates auth succeeded. This is hazardous because
53 * debug messages are more likely to change between OpenSSH releases.
54 * Thus, we could become incompatible with new OpenSSH releases.
57 #include "ksshprocess.h"
59 #include <config-runtime.h>
64 #ifdef HAVE_SYS_TIME_H
68 #include <kstandarddirs.h>
73 * The following are tables of string and regexps we match
74 * against the output of ssh. An entry in each array
75 * corresponds to the version of ssh found in versionStrs[].
77 * The version strings must be ordered in the array from most
78 * specific to least specific in cases where the beginning
79 * of several version strings are the similar. For example,
80 * consider the openssh version strings. The generic "OpenSSH"
81 * must be the last of the openssh version strings in the array
82 * so that is matched last. We use these generic version strings
83 * so we can do a best effor to support unknown ssh versions.
85 QRegExp
KSshProcess::versionStrs
[] = {
86 QRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"),
88 QRegExp("SSH Secure Shell"),
89 QRegExp("plink: Release")
92 const char * const KSshProcess::passwordPrompt
[] = {
93 "password:", // OpenSSH
94 "password:", // OpenSSH
99 const char * const KSshProcess::passphrasePrompt
[] = {
100 "Enter passphrase for key",
101 "Enter passphrase for key",
102 "Passphrase for key",
106 const char * const KSshProcess::authSuccessMsg
[] = {
107 "Authentication succeeded",
108 "ssh-userauth2 successful",
109 "Received SSH_CROSS_AUTHENTICATED packet",
110 "Started a shell/command"
113 const char* const KSshProcess::authFailedMsg
[] = {
114 "Permission denied (",
115 "Permission denied (",
116 "Authentication failed.",
120 const char* const KSshProcess::tryAgainMsg
[] = {
123 "adjfhjsdhfdsjfsjdfhuefeufeuefe",
127 QRegExp
KSshProcess::hostKeyMissingMsg
[] = {
128 QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
129 QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
130 QRegExp("Host key not found from database"),
134 const char* const KSshProcess::continuePrompt
[] = {
135 "Are you sure you want to continue connecting (yes/no)?",
136 "Are you sure you want to continue connecting (yes/no)?",
137 "Are you sure you want to continue connecting (yes/no)?",
141 const char* const KSshProcess::hostKeyChangedMsg
[] = {
142 "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
143 "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
144 "WARNING: HOST IDENTIFICATION HAS CHANGED!",
148 QRegExp
KSshProcess::keyFingerprintMsg
[] = {
149 QRegExp("..(:..){15}"),
150 QRegExp("..(:..){15}"),
151 QRegExp(".....(-.....){10}"),
155 QRegExp
KSshProcess::knownHostsFileMsg
[] = {
156 QRegExp("Add correct host key in (.*) to get rid of this message."),
157 QRegExp("Add correct host key in (.*) to get rid of this message."),
158 QRegExp("Add correct host key to \"(.*)\""),
163 // This prompt only applies to commercial ssh.
164 const char* const KSshProcess::changeHostKeyOnDiskPrompt
[] = {
165 "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf",
166 "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf",
167 "Do you want to change the host key on disk (yes/no)?",
171 // We need this in addition the authFailedMsg because when
172 // OpenSSH gets a changed host key it will fail to connect
173 // depending on the StrictHostKeyChecking option. Depending
174 // how this option is set, it will print "Permission denied"
175 // and quit, or print "Host key verification failed." and
176 // quit. The later if StrictHostKeyChecking is "no".
177 // The former if StrictHostKeyChecking is
178 // "yes" or explicitly set to "ask".
179 QRegExp
KSshProcess::hostKeyVerifyFailedMsg
[] = {
180 QRegExp("Host key verification failed\\."),
181 QRegExp("Host key verification failed\\."),
182 QRegExp("Disconnected; key exchange or algorithm? negotiation failed \\(Key exchange failed\\.\\)\\."),
186 const char * const KSshProcess::connectionClosedMsg
[] = {
187 "Connection closed by remote host",
188 "Connection closed by remote host",
189 "Connection closed by remote host",
194 void KSshProcess::SIGCHLD_handler(int)
196 while(waitpid(-1, NULL
, WNOHANG
) > 0) {
201 void KSshProcess::installSignalHandlers() {
203 struct sigaction act
;
204 memset(&act
,0,sizeof(act
));
205 act
.sa_handler
= SIGCHLD_handler
;
214 sigaction(SIGCHLD
,&act
,NULL
);
218 void KSshProcess::removeSignalHandlers() {
220 struct sigaction act
;
221 memset(&act
,0,sizeof(act
));
222 act
.sa_handler
= SIG_DFL
;
223 sigaction(SIGCHLD
,&act
,NULL
);
227 KSshProcess::KSshProcess()
228 : mVersion(UNKNOWN_VER
), mConnected(false),
229 mRunning(false), mConnectState(0) {
231 mSshPath
= KStandardDirs::findExe(QString::fromLatin1("ssh"));
233 mSshPath
= KStandardDirs::findExe(QString::fromLatin1("plink"));
235 kDebug(KSSHPROC
) << "KSshProcess::KSshProcess(): ssh path [" <<
236 mSshPath
<< "]" << endl
;
238 installSignalHandlers();
241 KSshProcess::KSshProcess(QString pathToSsh
)
242 : mSshPath(pathToSsh
), mVersion(UNKNOWN_VER
), mConnected(false),
243 mRunning(false), mConnectState(0) {
244 installSignalHandlers();
247 KSshProcess::~KSshProcess(){
249 removeSignalHandlers();
250 while(waitpid(-1, NULL
, WNOHANG
) > 0) {
255 bool KSshProcess::setSshPath(QString pathToSsh
) {
256 mSshPath
= pathToSsh
;
258 if( mVersion
== UNKNOWN_VER
)
264 KSshProcess::SshVersion
KSshProcess::version() {
266 cmd
= mSshPath
+" -V 2>&1";
268 // Get version string from ssh client.
270 if( (p
= popen(cmd
.toLatin1(), "r")) == NULL
) {
271 kDebug(KSSHPROC
) << "KSshProcess::version(): "
272 "failed to start ssh: " << strerror(errno
) << endl
;
276 // Determine of the version from the version string.
279 if( (len
= fread(buf
, sizeof(char), sizeof(buf
)-1, p
)) == 0 ) {
280 kDebug(KSSHPROC
) << "KSshProcess::version(): "
281 "Read of ssh version string failed " <<
282 strerror(ferror(p
)) << endl
;
285 if( pclose(p
) == -1 ) {
286 kError(KSSHPROC
) << "KSshProcess::version(): pclose failed." << endl
;
291 kDebug(KSSHPROC
) << "KSshProcess::version(): "
292 "got version string [" << ver
<< "]" << endl
;
294 mVersion
= UNKNOWN_VER
;
295 for(int i
= 0; i
< SSH_VER_MAX
; i
++) {
296 if( ver
.indexOf(versionStrs
[i
]) != -1 ) {
297 mVersion
= (SshVersion
)i
;
302 kDebug(KSSHPROC
) << "KSshPRocess::version(): version number = "
305 if( mVersion
== UNKNOWN_VER
) {
306 kDebug(KSSHPROC
) << "KSshProcess::version(): "
307 "Sorry, I don't know about this version of ssh" << endl
;
308 mError
= ERR_UNKNOWN_VERSION
;
315 QString KSshProcess::versionStr() {
316 if( mVersion == UNKNOWN_VER ) {
318 if( mVersion == UNKNOWN_VER )
322 return QString::fromLatin1(versionStrs[mVersion]);
326 bool KSshProcess::setOptions(const SshOptList
& opts
) {
327 kDebug(KSSHPROC
) << "KSshProcess::setOptions()";
329 if( mVersion
== UNKNOWN_VER
) {
330 // we don't know the ssh version yet, so find out
332 if( mVersion
== UNKNOWN_VER
) {
338 SshOptListConstIterator it
;
339 QString cmd
, subsystem
;
340 mPassword
= mUsername
= mHost
= QString();
342 for(it
= opts
.begin(); it
!= opts
.end(); ++it
) {
343 //kDebug(KSSHPROC) << "opt.opt = " << (*it).opt;
344 //kDebug(KSSHPROC) << "opt.str = " << (*it).str;
345 //kDebug(KSSHPROC) << "opt.num = " << (*it).num;
346 switch( (*it
).opt
) {
352 subsystem
= (*it
).str
;
356 if( mVersion
== PLINK
) {
361 tmp
.setNum((*it
).num
);
372 mArgs
.append((*it
).str
.toLatin1());
373 mUsername
= (*it
).str
;
377 mPassword
= (*it
).str
;
381 if( mVersion
<= OPENSSH
) {
383 tmp
+= QString::number((*it
).num
).toLatin1();
387 else if( mVersion
<= SSH
|| mVersion
== PLINK
) {
388 if( (*it
).num
== 1 ) {
391 // else uses version 2 by default
396 if( mVersion
== PLINK
) {
397 mArgs
.append((*it
).boolean
? "-X" : "-x");
400 tmp
+= (*it
).boolean
? "yes" : "no";
406 case SSH_FORWARDAGENT
:
407 if( mVersion
== PLINK
) {
408 mArgs
.append((*it
).boolean
? "-A" : "-a");
410 tmp
= "ForwardAgent=";
411 tmp
+= (*it
).boolean
? "yes" : "no";
417 case SSH_ESCAPE_CHAR
:
418 if( mVersion
!= PLINK
) {
419 if( (*it
).num
== -1 )
422 tmp
= QByteArray( (char)((*it
).num
), '\0' );
429 // don't allow NumberOfPasswordPrompts or StrictHostKeyChecking
430 // since KSshProcess depends on specific setting of these for
431 // preforming authentication correctly.
432 if( mVersion
!= PLINK
) {
433 tmp
= (*it
).str
.toLatin1();
434 if( tmp
.contains("NumberOfPasswordPrompts") ||
435 tmp
.contains("StrictHostKeyChecking") ) {
436 mError
= ERR_INVALID_OPT
;
451 kDebug(KSSHPROC
) << "KSshProcess::setOptions(): "
452 "unrecognized ssh opt " << (*it
).opt
<< endl
;
456 if( !subsystem
.isEmpty() && !cmd
.isEmpty() ) {
457 kDebug(KSSHPROC
) << "KSshProcess::setOptions(): "
458 "cannot use a subsystem and command at the same time" << endl
;
459 mError
= ERR_CMD_SUBSYS_CONFLICT
;
460 mErrorMsg
= i18n("Cannot specify a subsystem and command at the same time.");
464 // These options govern the behavior of ssh and
465 // cannot be defined by the user
466 //mArgs.append("-o");
467 //mArgs.append("StrictHostKeyChecking=ask");
468 mArgs
.append("-v"); // So we get a message that the
469 // connection was successful
470 if( mVersion
<= OPENSSH
) {
473 else if( mVersion
<= SSH
) {
474 mArgs
.append("-o"); // So we can check if the connection was successful
475 mArgs
.append("AuthenticationSuccessMsg=yes");
478 if( mHost
.isEmpty() ) {
479 kDebug(KSSHPROC
) << "KSshProcess::setOptions(): "
480 "a host name must be supplied" << endl
;
484 mArgs
.append(mHost
.toLatin1());
487 if( !subsystem
.isEmpty() ) {
489 mArgs
.append(subsystem
.toLatin1());
492 if( !cmd
.isEmpty() ) {
493 mArgs
.append(cmd
.toLatin1());
499 void KSshProcess::printArgs() {
500 QList
<QByteArray
>::Iterator it
;
501 for( it
= mArgs
.begin(); it
!= mArgs
.end(); ++it
) {
502 kDebug(KSSHPROC
) << "arg: " << *it
;
507 int KSshProcess::error(QString
& msg
) {
508 kDebug(KSSHPROC
) << "KSshProcess::error()";
509 kDebug() << mErrorMsg
;
514 void KSshProcess::kill(int signal
) {
517 kDebug(KSSHPROC
) << "KSshProcess::kill(signal:" << signal
518 << "): ssh pid is " << pid
<< endl
;
519 kDebug(KSSHPROC
) << "KSshPRocess::kill(): we are "
520 << (mConnected
? "" : "not ") << "connected" << endl
;
521 kDebug(KSSHPROC
) << "KSshProcess::kill(): we are "
522 << (mRunning
? "" : "not ") << "running a ssh process" << endl
;
524 if( mRunning
&& pid
> 1 ) {
525 // Kill the child process...
526 if ( ::kill(pid
, signal
) == 0 ) {
527 // clean up if we tried to kill the process
528 if( signal
== SIGTERM
|| signal
== SIGKILL
) {
529 while(waitpid(-1, NULL
, WNOHANG
) > 0) {
537 kDebug(KSSHPROC
) << "KSshProcess::kill(): kill failed";
540 kDebug(KSSHPROC
) << "KSshProcess::kill(): "
541 "Refusing to kill ssh process" << endl
;
547 * Try to open an ssh connection.
548 * SSH prints certain messages to certain file descriptiors:
549 * passwordPrompt - pty
550 * passphrasePrompt - pty
551 * authSuccessMsg - stderr (OpenSSH),
552 * authFailedMsg - stderr
553 * hostKeyMissing - stderr
554 * hostKeyChanged - stderr
555 * continuePrompt - stderr
557 * We will use a select to wait for a line on each descriptor. Then get
558 * each line that available and take action based on it. The type
559 * of messages we are looking for and the action we take on each
561 * passwordPrompt - Return false, set error to ERR_NEED_PASSWD.
562 * On the next call to connect() we expect a password
565 * passpharsePrompt - Return false, set error to ERR_NEED_PASSPHRASE.
566 * On the next call to connect() we expect a
567 * passphrase to be available.
569 * authSuccessMsg - Return true, as we have successfully established a
572 * authFailedMsg - Return false, set error to ERR_AUTH_FAILED. We
573 * were unable to authenticate the connection given
574 * the available authentication information.
576 * hostKeyMissing - Return false, set error to ERR_NEW_HOST_KEY. Caller
577 * must call KSshProcess.acceptHostKey(bool) to accept
578 * or reject the key before calling connect() again.
580 * hostKeyChanged - Return false, set error to ERR_DIFF_HOST_KEY. Caller
581 * must call KSshProcess.acceptHostKey(bool) to accept
582 * or reject the key before calling connect() again.
584 * continuePrompt - Send 'yes' or 'no' to accept or reject a key,
590 void KSshProcess::acceptHostKey(bool accept
) {
591 kDebug(KSSHPROC
) << "KSshProcess::acceptHostKey(accept:"
592 << accept
<< ")" << endl
;
593 mAcceptHostKey
= accept
;
596 void KSshProcess::setPassword(QString password
) {
597 kDebug(KSSHPROC
) << "KSshProcess::setPassword(password:xxxxxxxx)";
598 mPassword
= password
;
601 QString
KSshProcess::getLine() {
603 return ssh
.readLineFromPty(false);
605 static QStringList buffer
;
606 QString line
= QString();
607 QByteArray ptyLine
, errLine
;
609 if( buffer
.empty() ) {
610 // PtyProcess buffers lines. First check that there
611 // isn't something on the PtyProces buffer or that there
612 // is not data ready to be read from the pty or stderr.
613 ptyLine
= ssh
.readLineFromPty(false);
614 errLine
= ssh
.readLineFromStderr(false);
616 // If PtyProcess did have something for us, get it and
617 // place it in our line buffer.
618 if( ! ptyLine
.isEmpty() ) {
619 buffer
.prepend(QString(ptyLine
));
622 if( ! errLine
.isEmpty() ) {
623 buffer
.prepend(QString(errLine
));
626 // If we still don't have anything in our buffer so there must
627 // not be anything on the pty or stderr. Setup a select()
628 // to wait for some data from SSH.
629 if( buffer
.empty() ) {
630 //kDebug(KSSHPROC) << "KSshProcess::getLine(): " <<
631 // "Line buffer empty, calling select() to wait for data." << endl;
632 int errfd
= ssh
.stderrFd();
633 int ptyfd
= ssh
.fd();
638 // find max file descriptor
639 int maxfd
= ptyfd
> errfd
? ptyfd
: errfd
;
642 FD_SET(ptyfd
, &rfds
); // Add pty file descriptor
643 FD_SET(errfd
, &rfds
); // Add std error file descriptor
646 FD_SET(ptyfd
, &efds
);
647 FD_SET(errfd
, &efds
);
649 tv
.tv_sec
= 60; tv
.tv_usec
= 0; // 60 second timeout
651 // Wait for a message from ssh on stderr or the pty.
654 ret
= ::select(maxfd
+1, &rfds
, NULL
, &efds
, &tv
);
655 while( ret
== -1 && errno
== EINTR
);
657 // Handle any errors from select
659 kDebug(KSSHPROC
) << "KSshProcess::connect(): " <<
660 "timed out waiting for a response" << endl
;
661 mError
= ERR_TIMED_OUT
;
664 else if( ret
== -1 ) {
665 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
666 << "select error: " << strerror(errno
) << endl
;
667 mError
= ERR_INTERNAL
;
671 // We are not respecting any type of order in which the
672 // lines were received. Who knows whether pty or stderr
673 // had data on it first.
674 if( FD_ISSET(ptyfd
, &rfds
) ) {
675 ptyLine
= ssh
.readLineFromPty(false);
676 buffer
.prepend(QString(ptyLine
));
677 //kDebug(KSSHPROC) << "KSshProcess::getLine(): "
678 // "line from pty -" << ptyLine << endl;
681 if( FD_ISSET(errfd
, &rfds
) ) {
682 errLine
= ssh
.readLineFromStderr(false);
683 buffer
.prepend(QString(errLine
));
684 //kDebug(KSSHPROC) << "KSshProcess::getLine(): "
685 // "line from err -" << errLine << endl;
688 if( FD_ISSET(ptyfd
, &efds
) ) {
689 kDebug(KSSHPROC
) << "KSshProcess::getLine(): "
690 "Exception on pty file descriptor." << endl
;
693 if( FD_ISSET(errfd
, &efds
) ) {
694 kDebug(KSSHPROC
) << "KSshProcess::getLine(): "
695 "Exception on std err file descriptor." << endl
;
701 // We should have something in our buffer now.
702 // Return the last line.
707 line
= buffer
.last();
710 if( line
.isNull() && buffer
.count() > 0 ) {
711 line
= buffer
.last();
715 // kDebug(KSSHPROC) << "KSshProcess::getLine(): " <<
716 // buffer.count() << " lines in buffer" << endl;
717 kDebug(KSSHPROC
) << "KSshProcess::getLine(): "
718 "ssh: " << line
<< endl
;
725 // All the different states we could go through while trying to connect.
726 enum sshConnectState
{
727 STATE_START
, STATE_TRY_PASSWD
, STATE_WAIT_PROMPT
, STATE_NEW_KEY_CONTINUE
,
728 STATE_DIFF_KEY_CONTINUE
, STATE_FATAL
, STATE_WAIT_CONTINUE_PROMPT
,
729 STATE_SEND_CONTINUE
, STATE_AUTH_FAILED
, STATE_NEW_KEY_WAIT_CONTINUE
,
730 STATE_DIFF_KEY_WAIT_CONTINUE
, STATE_TRY_PASSPHRASE
733 // Print the state as a string. Good for debugging
734 const char* stateStr(int state
) {
737 return "STATE_START";
738 case STATE_TRY_PASSWD
:
739 return "STATE_TRY_PASSWD";
740 case STATE_WAIT_PROMPT
:
741 return "STATE_WAIT_PROMPT";
742 case STATE_NEW_KEY_CONTINUE
:
743 return "STATE_NEW_KEY_CONTINUE";
744 case STATE_DIFF_KEY_CONTINUE
:
745 return "STATE_DIFF_KEY_CONTINUE";
747 return "STATE_FATAL";
748 case STATE_WAIT_CONTINUE_PROMPT
:
749 return "STATE_WAIT_CONTINUE_PROMPT";
750 case STATE_SEND_CONTINUE
:
751 return "STATE_SEND_CONTINE";
752 case STATE_AUTH_FAILED
:
753 return "STATE_AUTH_FAILED";
754 case STATE_NEW_KEY_WAIT_CONTINUE
:
755 return "STATE_NEW_KEY_WAIT_CONTINUE";
756 case STATE_DIFF_KEY_WAIT_CONTINUE
:
757 return "STATE_DIFF_KEY_WAIT_CONTINUE";
758 case STATE_TRY_PASSPHRASE
:
759 return "STATE_TRY_PASSPHRASE";
764 bool KSshProcess::connect() {
765 if( mVersion
== UNKNOWN_VER
) {
766 // we don't know the ssh version yet, so find out
768 if( mVersion
== UNKNOWN_VER
) {
773 // We'll put a limit on the number of state transitions
774 // to ensure we don't go out of control.
775 int transitionLimit
= 500;
777 while(--transitionLimit
) {
778 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
779 << "Connect state " << stateStr(mConnectState
) << endl
;
781 QString line
; // a line from ssh
782 QString msgBuf
; // buffer for important messages from ssh
783 // which are to be returned to the user
785 switch(mConnectState
) {
787 // Executes the ssh binary with the options provided. If no options
788 // have been specified, sets error and returns false. Continue to
789 // state 1 if execution is successful, otherwise set error and
792 // reset some key values to safe values
793 mAcceptHostKey
= false;
794 mKeyFingerprint
.clear();
795 mKnownHostsFile
.clear();
797 if( mArgs
.isEmpty() ) {
798 kDebug(KSSHPROC
) << "KSshProcess::connect(): ssh options "
799 "need to be set first using setArgs()" << endl
;
800 mError
= ERR_NO_OPTIONS
;
801 mErrorMsg
= i18n("No options provided for ssh execution.");
805 if( ssh
.exec(mSshPath
.toLatin1(), mArgs
) ) {
807 "KSshProcess::connect(): ssh exec failed" << endl
;
808 mError
= ERR_CANNOT_LAUNCH
;
809 mErrorMsg
= i18n("Failed to execute ssh process.");
813 kDebug(KSSHPROC
) << "KSshPRocess::connect(): ssh pid = " << ssh
.pid();
815 // set flag to indicate what have started a ssh process
817 mConnectState
= STATE_WAIT_PROMPT
;
820 // STATE_WAIT_PROMPT:
821 // Get a line of input from the ssh process. Check the contents
822 // of the line to determine the next state. Ignore the line
823 // if we don't recognize its contents. If the line contains
824 // the continue prompt, we have an error since we should never
825 // get that line in this state. Set ERR_INVALID_STATE error
827 case STATE_WAIT_PROMPT
:
829 if( line
.isNull() ) {
830 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
831 "Got null line in STATE_WAIT_PROMPT." << endl
;
832 mError
= ERR_INTERACT
;
834 i18n("Error encountered while talking to ssh.");
835 mConnectState
= STATE_FATAL
;
837 else if( line
.indexOf(QString::fromLatin1(passwordPrompt
[mVersion
]), 0, Qt::CaseInsensitive
) != -1 ) {
838 mConnectState
= STATE_TRY_PASSWD
;
840 else if( line
.indexOf(passphrasePrompt
[mVersion
]) != -1 ) {
841 mConnectState
= STATE_TRY_PASSPHRASE
;
843 else if( line
.indexOf(authSuccessMsg
[mVersion
]) != -1 ) {
846 else if( (line
.indexOf(authFailedMsg
[mVersion
]) != -1)
847 && (line
.indexOf(tryAgainMsg
[mVersion
]) == -1) ) {
848 mConnectState
= STATE_AUTH_FAILED
;
850 else if( line
.indexOf(hostKeyMissingMsg
[mVersion
]) != -1 ) {
851 mConnectState
= STATE_NEW_KEY_WAIT_CONTINUE
;
853 else if( line
.indexOf(hostKeyChangedMsg
[mVersion
]) != -1 ) {
854 mConnectState
= STATE_DIFF_KEY_WAIT_CONTINUE
;
856 else if( line
.indexOf(continuePrompt
[mVersion
]) != -1 ) {
857 //mConnectState = STATE_SEND_CONTINUE;
858 kDebug(KSSHPROC
) << "KSshProcess:connect(): "
859 "Got continue prompt where we shouldn't (STATE_WAIT_PROMPT)"
861 mError
= ERR_INTERACT
;
863 i18n("Error encountered while talking to ssh.");
865 else if( line
.indexOf(connectionClosedMsg
[mVersion
]) != -1 ) {
866 mConnectState
= STATE_FATAL
;
867 mError
= ERR_CLOSED_BY_REMOTE_HOST
;
868 mErrorMsg
= i18n("Connection closed by remote host.");
870 else if( line
.indexOf(changeHostKeyOnDiskPrompt
[mVersion
]) != -1 ) {
871 // always say yes to this. It always comes after commercial ssh
872 // prints a "continue to connect prompt". We assume that if the
873 // user choose to continue, then they also want to save the
875 ssh
.writeLine("yes");
883 // If we have password send it to the ssh process, else
884 // set error ERR_NEED_PASSWD and return false to the caller.
885 // The caller then must then call KSshProcess::setPassword(QString)
886 // before calling KSshProcess::connect() again.
888 // Almost exactly liek STATE_TRY_PASSPHRASE. Check there if you
889 // make changes here.
890 case STATE_TRY_PASSWD
:
891 // We have a password prompt waiting for us to supply
892 // a password. Send that password to ssh. If the caller
893 // did not supply a password like we asked, then ask
895 if( !mPassword
.isEmpty() ) {
897 ssh
.writeLine(mPassword
.toLatin1());
899 // Overwrite the password so it isn't in memory.
900 mPassword
.fill(QChar('X'));
902 // Set the password to null so we will request another
903 // password if this one fails.
906 mConnectState
= STATE_WAIT_PROMPT
;
909 kDebug(KSSHPROC
) << "KSshProcess::connect() "
910 "Need password from caller." << endl
;
911 // The caller needs to supply a password before
912 // connecting can continue.
913 mError
= ERR_NEED_PASSWD
;
914 mErrorMsg
= i18n("Please supply a password.");
915 mConnectState
= STATE_TRY_PASSWD
;
920 // STATE_TRY_KEY_PASSPHRASE:
921 // If we have passphrase send it to the ssh process, else
922 // set error ERR_NEED_PASSPHRASE and return false to the caller.
923 // The caller then must then call KSshProcess::setPassword(QString)
924 // before calling KSshProcess::connect() again.
926 // Almost exactly like STATE_TRY_PASSWD. The only difference is
927 // the error we set if we don't have a passphrase. We duplicate
928 // this code to keep in the spirit of the state machine.
929 case STATE_TRY_PASSPHRASE
:
930 // We have a passphrase prompt waiting for us to supply
931 // a passphrase. Send that passphrase to ssh. If the caller
932 // did not supply a passphrase like we asked, then ask
934 if( !mPassword
.isEmpty() ) {
936 ssh
.writeLine(mPassword
.toLatin1());
938 // Overwrite the password so it isn't in memory.
939 mPassword
.fill(QChar('X'));
941 // Set the password to null so we will request another
942 // password if this one fails.
945 mConnectState
= STATE_WAIT_PROMPT
;
948 kDebug(KSSHPROC
) << "KSshProcess::connect() "
949 "Need passphrase from caller." << endl
;
950 // The caller needs to supply a passphrase before
951 // connecting can continue.
952 mError
= ERR_NEED_PASSPHRASE
;
953 mErrorMsg
= i18n("Please supply the passphrase for "
954 "your SSH private key.");
955 mConnectState
= STATE_TRY_PASSPHRASE
;
960 // STATE_AUTH_FAILED:
961 // Authentication has failed. Tell the caller by setting the
962 // ERR_AUTH_FAILED error and returning false. If
963 // auth has failed then ssh should have exited, but
964 // we will kill it to make sure.
965 case STATE_AUTH_FAILED
:
966 mError
= ERR_AUTH_FAILED
;
967 mErrorMsg
= i18n("Authentication to %1 failed", mHost
);
968 mConnectState
= STATE_FATAL
;
971 // STATE_NEW_KEY_WAIT_CONTINUE:
972 // Grab lines from ssh until we get a continue prompt or a auth
973 // denied. We will get the later if StrictHostKeyChecking is set
974 // to yes. Go to STATE_NEW_KEY_CONTINUE if we get a continue prompt.
975 case STATE_NEW_KEY_WAIT_CONTINUE
:
977 if( line
.isNull() ) {
978 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
979 "Got null line in STATE_NEW_KEY_WAIT_CONTINUE." << endl
;
980 mError
= ERR_INTERACT
;
982 i18n("Error encountered while talking to ssh.");
983 mConnectState
= STATE_FATAL
;
985 else if( ((line
.indexOf(authFailedMsg
[mVersion
]) != -1)
986 && (line
.indexOf(tryAgainMsg
[mVersion
]) == -1))
987 || (line
.indexOf(hostKeyVerifyFailedMsg
[mVersion
]) != -1) ) {
988 mError
= ERR_AUTH_FAILED_NEW_KEY
;
990 "The identity of the remote host '%1' could not be verified "
991 "because the host's key is not in the \"known hosts\" file."
994 if( mKnownHostsFile
.isEmpty() ) {
996 " Manually, add the host's key to the \"known hosts\" "
997 "file or contact your administrator."
1002 " Manually, add the host's key to %1 "
1003 "or contact your administrator."
1007 mConnectState
= STATE_FATAL
;
1009 else if( line
.indexOf(continuePrompt
[mVersion
]) != -1 ) {
1010 mConnectState
= STATE_NEW_KEY_CONTINUE
;
1012 else if( line
.indexOf(connectionClosedMsg
[mVersion
]) != -1 ) {
1013 mConnectState
= STATE_FATAL
;
1014 mError
= ERR_CLOSED_BY_REMOTE_HOST
;
1015 mErrorMsg
= i18n("Connection closed by remote host.");
1017 else if( line
.indexOf(keyFingerprintMsg
[mVersion
]) != -1 ) {
1018 mKeyFingerprint
= keyFingerprintMsg
[mVersion
].cap();
1019 kDebug(KSSHPROC
) << "Found key fingerprint: " << mKeyFingerprint
;
1020 mConnectState
= STATE_NEW_KEY_WAIT_CONTINUE
;
1028 // STATE_NEW_KEY_CONTINUE:
1029 // We got a continue prompt for the new key message. Set the error
1030 // message to reflect this, return false and hope for caller response.
1031 case STATE_NEW_KEY_CONTINUE
:
1032 mError
= ERR_NEW_HOST_KEY
;
1034 "The identity of the remote host '%1' could not be "
1035 "verified. The host's key fingerprint is:\n%2\nYou should "
1036 "verify the fingerprint with the host's administrator before "
1038 "Would you like to accept the host's key and connect anyway? "
1039 , mHost
, mKeyFingerprint
);
1040 mConnectState
= STATE_SEND_CONTINUE
;
1043 // STATE_DIFF_KEY_WAIT_CONTINUE:
1044 // Grab lines from ssh until we get a continue prompt or a auth
1045 // denied. We will get the later if StrictHostKeyChecking is set
1046 // to yes. Go to STATE_DIFF_KEY_CONTINUE if we get a continue prompt.
1047 case STATE_DIFF_KEY_WAIT_CONTINUE
:
1049 if( line
.isNull() ) {
1050 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
1051 "Got null line in STATE_DIFF_KEY_WAIT_CONTINUE." << endl
;
1052 mError
= ERR_INTERACT
;
1054 i18n("Error encountered while talking to ssh.");
1055 mConnectState
= STATE_FATAL
;
1057 else if( ((line
.indexOf(authFailedMsg
[mVersion
]) != -1)
1058 && (line
.indexOf(tryAgainMsg
[mVersion
]) == -1))
1059 || (line
.indexOf(hostKeyVerifyFailedMsg
[mVersion
]) != -1) ) {
1060 mError
= ERR_AUTH_FAILED_DIFF_KEY
;
1062 "WARNING: The identity of the remote host '%1' has changed!\n\n"
1063 "Someone could be eavesdropping on your connection, or the "
1064 "administrator may have just changed the host's key. "
1065 "Either way, you should verify the host's key fingerprint with the host's "
1066 "administrator. The key fingerprint is:\n%2\n"
1067 "Add the correct host key to \"%3\" to "
1068 "get rid of this message."
1069 , mHost
, mKeyFingerprint
, mKnownHostsFile
);
1070 mConnectState
= STATE_FATAL
;
1072 else if( line
.indexOf(continuePrompt
[mVersion
]) != -1 ) {
1073 mConnectState
= STATE_DIFF_KEY_CONTINUE
;
1075 else if( line
.indexOf(keyFingerprintMsg
[mVersion
]) != -1 ) {
1076 mKeyFingerprint
= keyFingerprintMsg
[mVersion
].cap();
1077 kDebug(KSSHPROC
) << "Found key fingerprint: " << mKeyFingerprint
;
1078 mConnectState
= STATE_DIFF_KEY_WAIT_CONTINUE
;
1080 else if( line
.indexOf(knownHostsFileMsg
[mVersion
]) != -1 ) {
1081 mKnownHostsFile
= (knownHostsFileMsg
[mVersion
]).cap(1);
1082 kDebug(KSSHPROC
) << "Found known hosts file name: " << mKnownHostsFile
;
1083 mConnectState
= STATE_DIFF_KEY_WAIT_CONTINUE
;
1090 // STATE_DIFF_KEY_CONTINUE:
1091 // We got a continue prompt for the different key message.
1092 // Set ERR_DIFF_HOST_KEY error
1093 // and return false to signal need to caller action.
1094 case STATE_DIFF_KEY_CONTINUE
:
1095 mError
= ERR_DIFF_HOST_KEY
;
1097 "WARNING: The identity of the remote host '%1' has changed!\n\n"
1098 "Someone could be eavesdropping on your connection, or the "
1099 "administrator may have just changed the host's key. "
1100 "Either way, you should verify the host's key fingerprint with the host's "
1101 "administrator before connecting. The key fingerprint is:\n%2\n\n"
1102 "Would you like to accept the host's new key and connect anyway?"
1103 , mHost
, mKeyFingerprint
);
1104 mConnectState
= STATE_SEND_CONTINUE
;
1107 // STATE_SEND_CONTINUE:
1108 // We found a continue prompt. Send our answer.
1109 case STATE_SEND_CONTINUE
:
1110 if( mAcceptHostKey
) {
1111 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
1112 "host key accepted" << endl
;
1113 ssh
.writeLine("yes");
1114 mConnectState
= STATE_WAIT_PROMPT
;
1117 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
1118 "host key rejected" << endl
;
1119 ssh
.writeLine("no");
1120 mError
= ERR_HOST_KEY_REJECTED
;
1121 mErrorMsg
= i18n("Host key was rejected.");
1122 mConnectState
= STATE_FATAL
;
1127 // Something bad happened that we cannot recover from.
1128 // Kill the ssh process and set flags to show we have
1129 // ended the connection and killed ssh.
1131 // mError and mErrorMsg should be set by the immediately
1137 mConnectState
= STATE_START
;
1138 // mError, mErroMsg set by last state
1142 kDebug(KSSHPROC
) << "KSshProcess::connect(): "
1143 "Invalid state number - " << mConnectState
<< endl
;
1144 mError
= ERR_INVALID_STATE
;
1145 mConnectState
= STATE_FATAL
;
1149 // we should never get here
1150 kDebug(KSSHPROC
) << "KSshProcess::connect(): " <<
1151 "After switch(). We shouldn't be here." << endl
;
1152 mError
= ERR_INTERNAL
;
1156 void KSshProcess::disconnect() {
1160 mConnectState
= STATE_START
;