You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kioslave/sftp/ksshprocess.cpp

1105 lines
41 KiB

/***************************************************************************
ksshprocess.cpp - description
-------------------
begin : Tue Jul 31 2001
copyright : (C) 2001 by Lucas Fisher
email : ljfisher@purdue.edu
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
/*
* See the KSshProcess header for examples on use.
*
* This class uses a hacked version of the PTYProcess
* class. This was needed because the kdelibs PTYProcess does not provide
* access to the pty file descriptor which we need, because ssh prints the
* password prompt to the pty and reads the password from the pty. I don't
* feel I know enough about ptys to confidently modify the orignial
* PTYProcess class.
*
* To start ssh we take the arguments the user gave us
* in the SshOptList and build the ssh command arguments based on the version
* of ssh we are using. This command and its arguments are passed to
* PTYProcess for execution. Once ssh is started we scan each line of input
* from stdin, stderr, and the pty for recognizable strings. The recognizable
* strings are taken from several string tables. Each table contains a string
* for each specific version of ssh we support and a string for a generic
* version of OpenSSH and commercial SSH incase we don't recognized the
* specific ssh version strings (as when a new SSH version is released after
* a release of KSshProcess). There are tables for ssh version strings,
* password prompts, new host key errors, different host key errors,
* messages than indicate a successful connect, authentication errors, etc.
* If we find user interaction is necessary, for instance to provide a
* password or passphrase, we return a err code to the user who can send
* a message to KSshProcess, using one of several methods, to correct
* the error.
*
* Determining when the ssh connection has successfully authenticationed has
* proved to be the most difficult challenge. OpenSSH does not print a message
* on successful authentication, thus the only way to know is to send data
* and wait for a return. The problem here is sometimes it can take a bit
* to establish the connection (for example, do to DNS lookups). This means
* the user may be sitting there waiting for a connection that failed.
* Instead, ssh is always started with the verbose flag. Then we look for
* a message that indicates auth succeeded. This is hazardous because
* debug messages are more likely to change between OpenSSH releases.
* Thus, we could become incompatible with new OpenSSH releases.
*/
#include <config.h>
#include "ksshprocess.h"
#include <stdio.h>
#include <errno.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <kstandarddirs.h>
#include <klocale.h>
#include <tqregexp.h>
/*
* The following are tables of string and regexps we match
* against the output of ssh. An entry in each array
* corresponds the the version of ssh found in versionStrs[].
*
* The version strings must be ordered in the array from most
* specific to least specific in cases where the beginning
* of several version strings are the similar. For example,
* consider the openssh version strings. The generic "OpenSSH"
* must be the last of the openssh version strings in the array
* so that is matched last. We use these generic version strings
* so we can do a best effor to support unknown ssh versions.
*/
TQRegExp KSshProcess::versionStrs[] = {
TQRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"),
TQRegExp("OpenSSH"),
TQRegExp("SSH Secure Shell")
};
const char * const KSshProcess::passwordPrompt[] = {
"password:", // OpenSSH
"password:", // OpenSSH
"password:" // SSH
};
const char * const KSshProcess::passphrasePrompt[] = {
"Enter passphrase for key",
"Enter passphrase for key",
"Passphrase for key"
};
const char * const KSshProcess::authSuccessMsg[] = {
"Authentication succeeded",
"ssh-userauth2 successful",
"Received SSH_CROSS_AUTHENTICATED packet"
};
const char* const KSshProcess::authFailedMsg[] = {
"Permission denied (",
"Permission denied (",
"Authentication failed."
};
const char* const KSshProcess::tryAgainMsg[] = {
"please try again",
"please try again",
"adjfhjsdhfdsjfsjdfhuefeufeuefe"
};
TQRegExp KSshProcess::hostKeyMissingMsg[] = {
TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"),
TQRegExp("Host key not found from database")
};
const char* const KSshProcess::continuePrompt[] = {
"Are you sure you want to continue connecting (yes/no)?",
"Are you sure you want to continue connecting (yes/no)?",
"Are you sure you want to continue connecting (yes/no)?"
};
const char* const KSshProcess::hostKeyChangedMsg[] = {
"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
"WARNING: HOST IDENTIFICATION HAS CHANGED!"
};
TQRegExp KSshProcess::keyFingerprintMsg[] = {
TQRegExp("..(:..){15}"),
TQRegExp("..(:..){15}"),
TQRegExp(".....(-.....){10}")
};
TQRegExp KSshProcess::knownHostsFileMsg[] = {
TQRegExp("Add correct host key in (.*) to get rid of this message."),
TQRegExp("Add correct host key in (.*) to get rid of this message."),
TQRegExp("Add correct host key to \"(.*)\"")
};
// This prompt only applies to commerical ssh.
const char* const KSshProcess::changeHostKeyOnDiskPrompt[] = {
"as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf",
"as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf",
"Do you want to change the host key on disk (yes/no)?"
};
// We need this in addition the authFailedMsg because when
// OpenSSH gets a changed host key it will fail to connect
// depending on the StrictHostKeyChecking option. Depending
// how this option is set, it will print "Permission denied"
// and quit, or print "Host key verification failed." and
// quit. The later if StrictHostKeyChecking is "no".
// The former if StrictHostKeyChecking is
// "yes" or explicitly set to "ask".
TQRegExp KSshProcess::hostKeyVerifyFailedMsg[] = {
TQRegExp("Host key verification failed\\."),
TQRegExp("Host key verification failed\\."),
TQRegExp("Disconnected; key exchange or algorithm? negotiation failed \\(Key exchange failed\\.\\)\\.")
};
const char * const KSshProcess::connectionClosedMsg[] = {
"Connection closed by remote host",
"Connection closed by remote host",
"Connection closed by remote host"
};
void KSshProcess::SIGCHLD_handler(int) {
while(waitpid(-1, NULL, WNOHANG) > 0);
}
void KSshProcess::installSignalHandlers() {
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_handler = SIGCHLD_handler;
act.sa_flags = 0
#ifdef SA_NOCLDSTOP
| SA_NOCLDSTOP
#endif
#ifdef SA_RESTART
| SA_RESTART
#endif
;
sigaction(SIGCHLD,&act,NULL);
}
void KSshProcess::removeSignalHandlers() {
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_handler = SIG_DFL;
sigaction(SIGCHLD,&act,NULL);
}
KSshProcess::KSshProcess()
: mVersion(UNKNOWN_VER), mConnected(false),
mRunning(false), mConnectState(0) {
mSshPath = KStandardDirs::findExe(TQString::tqfromLatin1("ssh"));
kdDebug(KSSHPROC) << "KSshProcess::KSshProcess(): ssh path [" <<
mSshPath << "]" << endl;
installSignalHandlers();
}
KSshProcess::KSshProcess(TQString pathToSsh)
: mSshPath(pathToSsh), mVersion(UNKNOWN_VER), mConnected(false),
mRunning(false), mConnectState(0) {
installSignalHandlers();
}
KSshProcess::~KSshProcess(){
disconnect();
removeSignalHandlers();
while(waitpid(-1, NULL, WNOHANG) > 0);
}
bool KSshProcess::setSshPath(TQString pathToSsh) {
mSshPath = pathToSsh;
version();
if( mVersion == UNKNOWN_VER )
return false;
return true;
}
KSshProcess::SshVersion KSshProcess::version() {
TQString cmd;
cmd = mSshPath+" -V 2>&1";
// Get version string from ssh client.
FILE *p;
if( (p = popen(cmd.latin1(), "r")) == NULL ) {
kdDebug(KSSHPROC) << "KSshProcess::version(): "
"failed to start ssh: " << strerror(errno) << endl;
return UNKNOWN_VER;
}
// Determine of the version from the version string.
size_t len;
char buf[128];
if( (len = fread(buf, sizeof(char), sizeof(buf)-1, p)) == 0 ) {
kdDebug(KSSHPROC) << "KSshProcess::version(): "
"Read of ssh version string failed " <<
strerror(ferror(p)) << endl;
return UNKNOWN_VER;
}
if( pclose(p) == -1 ) {
kdError(KSSHPROC) << "KSshProcess::version(): pclose failed." << endl;
}
buf[len] = '\0';
TQString ver;
ver = buf;
kdDebug(KSSHPROC) << "KSshProcess::version(): "
"got version string [" << ver << "]" << endl;
mVersion = UNKNOWN_VER;
for(int i = 0; i < SSH_VER_MAX; i++) {
if( ver.tqfind(versionStrs[i]) != -1 ) {
mVersion = (SshVersion)i;
break;
}
}
kdDebug(KSSHPROC) << "KSshPRocess::version(): version number = "
<< mVersion << endl;
if( mVersion == UNKNOWN_VER ) {
kdDebug(KSSHPROC) << "KSshProcess::version(): "
"Sorry, I don't know about this version of ssh" << endl;
mError = ERR_UNKNOWN_VERSION;
return UNKNOWN_VER;
}
return mVersion;
}
/*
TQString KSshProcess::versionStr() {
if( mVersion == UNKNOWN_VER ) {
version();
if( mVersion == UNKNOWN_VER )
return TQString::null;
}
return TQString::tqfromLatin1(versionStrs[mVersion]);
}
*/
bool KSshProcess::setOptions(const SshOptList& opts) {
kdDebug(KSSHPROC) << "KSshProcess::setOptions()" << endl;
mArgs.clear();
SshOptListConstIterator it;
TQString cmd, subsystem;
mPassword = mUsername = mHost = TQString::null;
TQCString tmp;
for(it = opts.begin(); it != opts.end(); ++it) {
//kdDebug(KSSHPROC) << "opt.opt = " << (*it).opt << endl;
//kdDebug(KSSHPROC) << "opt.str = " << (*it).str << endl;
//kdDebug(KSSHPROC) << "opt.num = " << (*it).num << endl;
switch( (*it).opt ) {
case SSH_VERBOSE:
mArgs.append("-v");
break;
case SSH_SUBSYSTEM:
subsystem = (*it).str;
break;
case SSH_PORT:
mArgs.append("-p");
tmp.setNum((*it).num);
mArgs.append(tmp);
mPort = (*it).num;
break;
case SSH_HOST:
mHost = (*it).str;
break;
case SSH_USERNAME:
mArgs.append("-l");
mArgs.append((*it).str.latin1());
mUsername = (*it).str;
break;
case SSH_PASSWD:
mPassword = (*it).str;
break;
case SSH_PROTOCOL:
if( mVersion <= OPENSSH ) {
tmp = "Protocol=";
tmp += TQString::number((*it).num).latin1();
mArgs.append("-o");
mArgs.append(tmp);
}
else if( mVersion <= SSH ) {
if( (*it).num == 1 ) {
mArgs.append("-1");
}
// else uses version 2 by default
}
break;
case SSH_FORWARDX11:
tmp = "ForwardX11=";
tmp += (*it).boolean ? "yes" : "no";
mArgs.append("-o");
mArgs.append(tmp);
break;
case SSH_FORWARDAGENT:
tmp = "ForwardAgent=";
tmp += (*it).boolean ? "yes" : "no";
mArgs.append("-o");
mArgs.append(tmp);
break;
case SSH_ESCAPE_CHAR:
if( (*it).num == -1 )
tmp = "none";
else
tmp = (char)((*it).num);
mArgs.append("-e");
mArgs.append(tmp);
break;
case SSH_OPTION:
// don't allow NumberOfPasswordPrompts or StrictHostKeyChecking
// since KSshProcess depends on specific setting of these for
// preforming authentication correctly.
tmp = (*it).str.latin1();
if( tmp.tqcontains("NumberOfPasswordPrompts") ||
tmp.tqcontains("StrictHostKeyChecking") ) {
mError = ERR_INVALID_OPT;
return false;
}
else {
mArgs.append("-o");
mArgs.append(tmp);
}
break;
case SSH_COMMAND:
cmd = (*it).str;
break;
default:
kdDebug(KSSHPROC) << "KSshProcess::setOptions(): "
"unrecognized ssh opt " << (*it).opt << endl;
}
}
if( !subsystem.isEmpty() && !cmd.isEmpty() ) {
kdDebug(KSSHPROC) << "KSshProcess::setOptions(): "
"cannot use a subsystem and command at the same time" << endl;
mError = ERR_CMD_SUBSYS_CONFLICT;
mErrorMsg = i18n("Cannot specify a subsystem and command at the same time.");
return false;
}
// These options govern the behavior of ssh and
// cannot be defined by the user
//mArgs.append("-o");
//mArgs.append("StrictHostKeyChecking=ask");
mArgs.append("-v"); // So we get a message that the
// connection was successful
if( mVersion <= OPENSSH ) {
// nothing
}
else if( mVersion <= SSH ) {
mArgs.append("-o"); // So we can check if the connection was successful
mArgs.append("AuthenticationSuccessMsg=yes");
}
if( mHost.isEmpty() ) {
kdDebug(KSSHPROC) << "KSshProcess::setOptions(): "
"a host name must be supplied" << endl;
return false;
}
else {
mArgs.append(mHost.latin1());
}
if( !subsystem.isEmpty() ) {
mArgs.append("-s");
mArgs.append(subsystem.latin1());
}
if( !cmd.isEmpty() ) {
mArgs.append(cmd.latin1());
}
return true;
}
void KSshProcess::printArgs() {
TQValueListIterator<TQCString> it;
for( it = mArgs.begin(); it != mArgs.end(); ++it) {
kdDebug(KSSHPROC) << "arg: " << *it << endl;
}
}
int KSshProcess::error(TQString& msg) {
kdDebug(KSSHPROC) << "KSshProcess::error()" << endl;
kdDebug() << mErrorMsg << endl;
msg = mErrorMsg;
return mError;
}
void KSshProcess::kill(int signal) {
int pid = ssh.pid();
kdDebug(KSSHPROC) << "KSshProcess::kill(signal:" << signal
<< "): ssh pid is " << pid << endl;
kdDebug(KSSHPROC) << "KSshPRocess::kill(): we are "
<< (mConnected ? "" : "not ") << "connected" << endl;
kdDebug(KSSHPROC) << "KSshProcess::kill(): we are "
<< (mRunning ? "" : "not ") << "running a ssh process" << endl;
if( mRunning && pid > 1 ) {
// Kill the child process...
if ( ::kill(pid, signal) == 0 ) {
// clean up if we tried to kill the process
if( signal == SIGTERM || signal == SIGKILL ) {
while(waitpid(-1, NULL, WNOHANG) > 0);
mConnected = false;
mRunning = false;
}
}
else
kdDebug(KSSHPROC) << "KSshProcess::kill(): kill failed" << endl;
}
else
kdDebug(KSSHPROC) << "KSshProcess::kill(): "
"Refusing to kill ssh process" << endl;
}
/**
* Try to open an ssh connection.
* SSH prints certain messages to certain file descriptiors:
* passwordPrompt - pty
* passphrasePrompt - pty
* authSuccessMsg - stderr (OpenSSH),
* authFailedMsg - stderr
* hostKeyMissing - stderr
* hostKeyChanged - stderr
* continuePrompt - stderr
*
* We will use a select to wait for a line on each descriptor. Then get
* each line that available and take action based on it. The type
* of messages we are looking for and the action we take on each
* message are:
* passwordPrompt - Return false, set error to ERR_NEED_PASSWD.
* On the next call to connect() we expect a password
* to be available.
*
* passpharsePrompt - Return false, set error to ERR_NEED_PASSPHRASE.
* On the next call to connect() we expect a
* passphrase to be available.
*
* authSuccessMsg - Return true, as we have successfully established a
* ssh connection.
*
* authFailedMsg - Return false, set error to ERR_AUTH_FAILED. We
* were unable to authenticate the connection given
* the available authentication information.
*
* hostKeyMissing - Return false, set error to ERR_NEW_HOST_KEY. Caller
* must call KSshProcess.acceptHostKey(bool) to accept
* or reject the key before calling connect() again.
*
* hostKeyChanged - Return false, set error to ERR_DIFF_HOST_KEY. Caller
* must call KSshProcess.acceptHostKey(bool) to accept
* or reject the key before calling connect() again.
*
* continuePrompt - Send 'yes' or 'no' to accept or reject a key,
* respectively.
*
*/
void KSshProcess::acceptHostKey(bool accept) {
kdDebug(KSSHPROC) << "KSshProcess::acceptHostKey(accept:"
<< accept << ")" << endl;
mAcceptHostKey = accept;
}
void KSshProcess::setPassword(TQString password) {
kdDebug(KSSHPROC) << "KSshProcess::setPassword(password:xxxxxxxx)" << endl;
mPassword = password;
}
TQString KSshProcess::getLine() {
static TQStringList buffer;
TQString line = TQString::null;
TQCString ptyLine, errLine;
if( buffer.empty() ) {
// PtyProcess buffers lines. First check that there
// isn't something on the PtyProces buffer or that there
// is not data ready to be read from the pty or stderr.
ptyLine = ssh.readLineFromPty(false);
errLine = ssh.readLineFromStderr(false);
// If PtyProcess did have something for us, get it and
// place it in our line buffer.
if( ! ptyLine.isEmpty() ) {
buffer.prepend(TQString(ptyLine));
}
if( ! errLine.isEmpty() ) {
buffer.prepend(TQString(errLine));
}
// If we still don't have anything in our buffer so there must
// not be anything on the pty or stderr. Setup a select()
// to wait for some data from SSH.
if( buffer.empty() ) {
//kdDebug(KSSHPROC) << "KSshProcess::getLine(): " <<
// "Line buffer empty, calling select() to wait for data." << endl;
int errfd = ssh.stderrFd();
int ptyfd = ssh.fd();
fd_set rfds;
fd_set efds;
struct timeval tv;
// find max file descriptor
int maxfd = ptyfd > errfd ? ptyfd : errfd;
FD_ZERO(&rfds);
FD_SET(ptyfd, &rfds); // Add pty file descriptor
FD_SET(errfd, &rfds); // Add std error file descriptor
FD_ZERO(&efds);
FD_SET(ptyfd, &efds);
FD_SET(errfd, &efds);
tv.tv_sec = 60; tv.tv_usec = 0; // 60 second timeout
// Wait for a message from ssh on stderr or the pty.
int ret = -1;
do
ret = ::select(maxfd+1, &rfds, NULL, &efds, &tv);
while( ret == -1 && errno == EINTR );
// Handle any errors from select
if( ret == 0 ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): " <<
"timed out waiting for a response" << endl;
mError = ERR_TIMED_OUT;
return TQString::null;
}
else if( ret == -1 ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
<< "select error: " << strerror(errno) << endl;
mError = ERR_INTERNAL;
return TQString::null;
}
// We are not respecting any type of order in which the
// lines were received. Who knows whether pty or stderr
// had data on it first.
if( FD_ISSET(ptyfd, &rfds) ) {
ptyLine = ssh.readLineFromPty(false);
buffer.prepend(TQString(ptyLine));
//kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
// "line from pty -" << ptyLine << endl;
}
if( FD_ISSET(errfd, &rfds) ) {
errLine = ssh.readLineFromStderr(false);
buffer.prepend(TQString(errLine));
//kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
// "line from err -" << errLine << endl;
}
if( FD_ISSET(ptyfd, &efds) ) {
kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
"Exception on pty file descriptor." << endl;
}
if( FD_ISSET(errfd, &efds) ) {
kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
"Exception on std err file descriptor." << endl;
}
}
}
// We should have something in our buffer now.
// Return the last line.
//it = buffer.end();
//line = *it;
//buffer.remove(it);
line = buffer.last();
buffer.pop_back();
if( line.isNull() && buffer.count() > 0 ) {
line = buffer.last();
buffer.pop_back();
}
// kdDebug(KSSHPROC) << "KSshProcess::getLine(): " <<
// buffer.count() << " lines in buffer" << endl;
kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
"ssh: " << line << endl;
return line;
}
// All the different states we could go through while trying to connect.
enum sshConnectState {
STATE_START, STATE_TRY_PASSWD, STATE_WAIT_PROMPT, STATE_NEW_KEY_CONTINUE,
STATE_DIFF_KEY_CONTINUE, STATE_FATAL, STATE_WAIT_CONTINUE_PROMPT,
STATE_SEND_CONTINUE, STATE_AUTH_FAILED, STATE_NEW_KEY_WAIT_CONTINUE,
STATE_DIFF_KEY_WAIT_CONTINUE, STATE_TRY_PASSPHRASE
};
// Print the state as a string. Good for debugging
const char* stateStr(int state) {
switch(state) {
case STATE_START:
return "STATE_START";
case STATE_TRY_PASSWD:
return "STATE_TRY_PASSWD";
case STATE_WAIT_PROMPT:
return "STATE_WAIT_PROMPT";
case STATE_NEW_KEY_CONTINUE:
return "STATE_NEW_KEY_CONTINUE";
case STATE_DIFF_KEY_CONTINUE:
return "STATE_DIFF_KEY_CONTINUE";
case STATE_FATAL:
return "STATE_FATAL";
case STATE_WAIT_CONTINUE_PROMPT:
return "STATE_WAIT_CONTINUE_PROMPT";
case STATE_SEND_CONTINUE:
return "STATE_SEND_CONTINE";
case STATE_AUTH_FAILED:
return "STATE_AUTH_FAILED";
case STATE_NEW_KEY_WAIT_CONTINUE:
return "STATE_NEW_KEY_WAIT_CONTINUE";
case STATE_DIFF_KEY_WAIT_CONTINUE:
return "STATE_DIFF_KEY_WAIT_CONTINUE";
case STATE_TRY_PASSPHRASE:
return "STATE_TRY_PASSPHRASE";
}
return "UNKNOWN";
}
bool KSshProcess::connect() {
if( mVersion == UNKNOWN_VER ) {
// we don't know the ssh version yet, so find out
version();
if( mVersion == -1 ) {
return false;
}
}
// We'll put a limit on the number of state transitions
// to ensure we don't go out of control.
int transitionLimit = 500;
while(--transitionLimit) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
<< "Connect state " << stateStr(mConnectState) << endl;
TQString line; // a line from ssh
TQString msgBuf; // buffer for important messages from ssh
// which are to be returned to the user
switch(mConnectState) {
// STATE_START:
// Executes the ssh binary with the options provided. If no options
// have been specified, sets error and returns false. Continue to
// state 1 if execution is successful, otherwise set error and
// return false.
case STATE_START:
// reset some key values to safe values
mAcceptHostKey = false;
mKeyFingerprint = TQString::null;
mKnownHostsFile = TQString::null;
if( mArgs.isEmpty() ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): ssh options "
"need to be set first using setArgs()" << endl;
mError = ERR_NO_OPTIONS;
mErrorMsg = i18n("No options provided for ssh execution.");
return false;
}
if( ssh.exec(mSshPath.latin1(), mArgs) ) {
kdDebug(KSSHPROC) <<
"KSshProcess::connect(): ssh exec failed" << endl;
mError = ERR_CANNOT_LAUNCH;
mErrorMsg = i18n("Failed to execute ssh process.");
return false;
}
kdDebug(KSSHPROC) << "KSshPRocess::connect(): ssh pid = " << ssh.pid() << endl;
// set flag to indicate what have started a ssh process
mRunning = true;
mConnectState = STATE_WAIT_PROMPT;
break;
// STATE_WAIT_PROMPT:
// Get a line of input from the ssh process. Check the contents
// of the line to determine the next state. Ignore the line
// if we don't recognize its contents. If the line contains
// the continue prompt, we have an error since we should never
// get that line in this state. Set ERR_INVALID_STATE error
// and return false.
case STATE_WAIT_PROMPT:
line = getLine();
if( line.isNull() ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"Got null line in STATE_WAIT_PROMPT." << endl;
mError = ERR_INTERACT;
mErrorMsg =
i18n("Error encountered while talking to ssh.");
mConnectState = STATE_FATAL;
}
else if( line.tqfind(TQString::tqfromLatin1(passwordPrompt[mVersion]), 0, false) != -1 ) {
mConnectState = STATE_TRY_PASSWD;
}
else if( line.tqfind(passphrasePrompt[mVersion]) != -1 ) {
mConnectState = STATE_TRY_PASSPHRASE;
}
else if( line.tqfind(authSuccessMsg[mVersion]) != -1 ) {
return true;
}
else if( line.tqfind(authFailedMsg[mVersion]) != -1
&& line.tqfind(tryAgainMsg[mVersion]) == -1 ) {
mConnectState = STATE_AUTH_FAILED;
}
else if( line.tqfind(hostKeyMissingMsg[mVersion]) != -1 ) {
mConnectState = STATE_NEW_KEY_WAIT_CONTINUE;
}
else if( line.tqfind(hostKeyChangedMsg[mVersion]) != -1 ) {
mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE;
}
else if( line.tqfind(continuePrompt[mVersion]) != -1 ) {
//mConnectState = STATE_SEND_CONTINUE;
kdDebug(KSSHPROC) << "KSshProcess:connect(): "
"Got continue prompt where we shouldn't (STATE_WAIT_PROMPT)"
<< endl;
mError = ERR_INTERACT;
mErrorMsg =
i18n("Error encountered while talking to ssh.");
}
else if( line.tqfind(connectionClosedMsg[mVersion]) != -1 ) {
mConnectState = STATE_FATAL;
mError = ERR_CLOSED_BY_REMOTE_HOST;
mErrorMsg = i18n("Connection closed by remote host.");
}
else if( line.tqfind(changeHostKeyOnDiskPrompt[mVersion]) != -1 ) {
// always say yes to this. It always comes after commerical ssh
// prints a "continue to connect prompt". We assume that if the
// user choose to continue, then they also want to save the
// host key to disk.
ssh.writeLine("yes");
}
else {
// ignore line
}
break;
// STATE_TRY_PASSWD:
// If we have password send it to the ssh process, else
// set error ERR_NEED_PASSWD and return false to the caller.
// The caller then must then call KSshProcess::setPassword(TQString)
// before calling KSshProcess::connect() again.
//
// Almost exactly liek STATE_TRY_PASSPHRASE. Check there if you
// make changes here.
case STATE_TRY_PASSWD:
// We have a password prompt waiting for us to supply
// a password. Send that password to ssh. If the caller
// did not supply a password like we asked, then ask
// again.
if( !mPassword.isEmpty() ) {
// ssh.WaitSlave();
ssh.writeLine(mPassword.latin1());
// Overwrite the password so it isn't in memory.
mPassword.fill(TQChar('X'));
// Set the password to null so we will request another
// password if this one fails.
mPassword = TQString::null;
mConnectState = STATE_WAIT_PROMPT;
}
else {
kdDebug(KSSHPROC) << "KSshProcess::connect() "
"Need password from caller." << endl;
// The caller needs to supply a password before
// connecting can continue.
mError = ERR_NEED_PASSWD;
mErrorMsg = i18n("Please supply a password.");
mConnectState = STATE_TRY_PASSWD;
return false;
}
break;
// STATE_TRY_KEY_PASSPHRASE:
// If we have passphrase send it to the ssh process, else
// set error ERR_NEED_PASSPHRASE and return false to the caller.
// The caller then must then call KSshProcess::setPassword(TQString)
// before calling KSshProcess::connect() again.
//
// Almost exactly like STATE_TRY_PASSWD. The only difference is
// the error we set if we don't have a passphrase. We duplicate
// this code to keep in the spirit of the state machine.
case STATE_TRY_PASSPHRASE:
// We have a passphrase prompt waiting for us to supply
// a passphrase. Send that passphrase to ssh. If the caller
// did not supply a passphrase like we asked, then ask
// again.
if( !mPassword.isEmpty() ) {
// ssh.WaitSlave();
ssh.writeLine(mPassword.latin1());
// Overwrite the password so it isn't in memory.
mPassword.fill(TQChar('X'));
// Set the password to null so we will request another
// password if this one fails.
mPassword = TQString::null;
mConnectState = STATE_WAIT_PROMPT;
}
else {
kdDebug(KSSHPROC) << "KSshProcess::connect() "
"Need passphrase from caller." << endl;
// The caller needs to supply a passphrase before
// connecting can continue.
mError = ERR_NEED_PASSPHRASE;
mErrorMsg = i18n("Please supply the passphrase for "
"your SSH private key.");
mConnectState = STATE_TRY_PASSPHRASE;
return false;
}
break;
// STATE_AUTH_FAILED:
// Authentication has failed. Tell the caller by setting the
// ERR_AUTH_FAILED error and returning false. If
// auth has failed then ssh should have exited, but
// we will kill it to make sure.
case STATE_AUTH_FAILED:
mError = ERR_AUTH_FAILED;
mErrorMsg = i18n("Authentication to %1 failed").arg(mHost);
mConnectState = STATE_FATAL;
break;
// STATE_NEW_KEY_WAIT_CONTINUE:
// Grab lines from ssh until we get a continue prompt or a auth
// denied. We will get the later if StrictHostKeyChecking is set
// to yes. Go to STATE_NEW_KEY_CONTINUE if we get a continue prompt.
case STATE_NEW_KEY_WAIT_CONTINUE:
line = getLine();
if( line.isNull() ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"Got null line in STATE_NEW_KEY_WAIT_CONTINUE." << endl;
mError = ERR_INTERACT;
mErrorMsg =
i18n("Error encountered while talking to ssh.");
mConnectState = STATE_FATAL;
}
else if( (line.tqfind(authFailedMsg[mVersion]) != -1
&& line.tqfind(tryAgainMsg[mVersion]) == -1)
|| line.tqfind(hostKeyVerifyFailedMsg[mVersion]) != -1 ) {
mError = ERR_AUTH_FAILED_NEW_KEY;
mErrorMsg = i18n(
"The identity of the remote host '%1' could not be verified "
"because the host's key is not in the \"known hosts\" file."
).arg(mHost);
if( mKnownHostsFile.isEmpty() ) {
mErrorMsg += i18n(
" Manually, add the host's key to the \"known hosts\" "
"file or contact your administrator."
);
}
else {
mErrorMsg += i18n(
" Manually, add the host's key to %1 "
"or contact your administrator."
).arg(mKnownHostsFile);
}
mConnectState = STATE_FATAL;
}
else if( line.tqfind(continuePrompt[mVersion]) != -1 ) {
mConnectState = STATE_NEW_KEY_CONTINUE;
}
else if( line.tqfind(connectionClosedMsg[mVersion]) != -1 ) {
mConnectState = STATE_FATAL;
mError = ERR_CLOSED_BY_REMOTE_HOST;
mErrorMsg = i18n("Connection closed by remote host.");
}
else if( line.tqfind(keyFingerprintMsg[mVersion]) != -1 ) {
mKeyFingerprint = keyFingerprintMsg[mVersion].cap();
kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl;
mConnectState = STATE_NEW_KEY_WAIT_CONTINUE;
}
else {
// ignore line
}
break;
// STATE_NEW_KEY_CONTINUE:
// We got a continue prompt for the new key message. Set the error
// message to reflect this, return false and hope for caller response.
case STATE_NEW_KEY_CONTINUE:
mError = ERR_NEW_HOST_KEY;
mErrorMsg = i18n(
"The identity of the remote host '%1' could not be "
"verified. The host's key fingerprint is:\n%2\nYou should "
"verify the fingerprint with the host's administrator before "
"connecting.\n\n"
"Would you like to accept the host's key and connect anyway? "
).arg(mHost).arg(mKeyFingerprint);
mConnectState = STATE_SEND_CONTINUE;
return false;
// STATE_DIFF_KEY_WAIT_CONTINUE:
// Grab lines from ssh until we get a continue prompt or a auth
// denied. We will get the later if StrictHostKeyChecking is set
// to yes. Go to STATE_DIFF_KEY_CONTINUE if we get a continue prompt.
case STATE_DIFF_KEY_WAIT_CONTINUE:
line = getLine();
if( line.isNull() ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"Got null line in STATE_DIFF_KEY_WAIT_CONTINUE." << endl;
mError = ERR_INTERACT;
mErrorMsg =
i18n("Error encountered while talking to ssh.");
mConnectState = STATE_FATAL;
}
else if( (line.tqfind(authFailedMsg[mVersion]) != -1
&& line.tqfind(tryAgainMsg[mVersion]) == -1)
|| line.tqfind(hostKeyVerifyFailedMsg[mVersion]) != -1 ) {
mError = ERR_AUTH_FAILED_DIFF_KEY;
mErrorMsg = i18n(
"WARNING: The identity of the remote host '%1' has changed!\n\n"
"Someone could be eavesdropping on your connection, or the "
"administrator may have just changed the host's key. "
"Either way, you should verify the host's key fingerprint with the host's "
"administrator. The key fingerprint is:\n%2\n"
"Add the correct host key to \"%3\" to "
"get rid of this message."
).arg(mHost).arg(mKeyFingerprint).arg(mKnownHostsFile);
mConnectState = STATE_FATAL;
}
else if( line.tqfind(continuePrompt[mVersion]) != -1 ) {
mConnectState = STATE_DIFF_KEY_CONTINUE;
}
else if( line.tqfind(keyFingerprintMsg[mVersion]) != -1 ) {
mKeyFingerprint = keyFingerprintMsg[mVersion].cap();
kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl;
mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE;
}
else if( line.tqfind(knownHostsFileMsg[mVersion]) != -1 ) {
mKnownHostsFile = (knownHostsFileMsg[mVersion]).cap(1);
kdDebug(KSSHPROC) << "Found known hosts file name: " << mKnownHostsFile << endl;
mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE;
}
else {
// ignore line
}
break;
// STATE_DIFF_KEY_CONTINUE:
// We got a continue prompt for the different key message.
// Set ERR_DIFF_HOST_KEY error
// and return false to signal need to caller action.
case STATE_DIFF_KEY_CONTINUE:
mError = ERR_DIFF_HOST_KEY;
mErrorMsg = i18n(
"WARNING: The identity of the remote host '%1' has changed!\n\n"
"Someone could be eavesdropping on your connection, or the "
"administrator may have just changed the host's key. "
"Either way, you should verify the host's key fingerprint with the host's "
"administrator before connecting. The key fingerprint is:\n%2\n\n"
"Would you like to accept the host's new key and connect anyway?"
).arg(mHost).arg(mKeyFingerprint);
mConnectState = STATE_SEND_CONTINUE;
return false;
// STATE_SEND_CONTINUE:
// We found a continue prompt. Send our answer.
case STATE_SEND_CONTINUE:
if( mAcceptHostKey ) {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"host key accepted" << endl;
ssh.writeLine("yes");
mConnectState = STATE_WAIT_PROMPT;
}
else {
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"host key rejected" << endl;
ssh.writeLine("no");
mError = ERR_HOST_KEY_REJECTED;
mErrorMsg = i18n("Host key was rejected.");
mConnectState = STATE_FATAL;
}
break;
// STATE_FATAL:
// Something bad happened that we cannot recover from.
// Kill the ssh process and set flags to show we have
// ended the connection and killed ssh.
//
// mError and mErrorMsg should be set by the immediately
// previous state.
case STATE_FATAL:
kill();
mConnected = false;
mRunning = false;
mConnectState = STATE_START;
// mError, mErroMsg set by last state
return false;
default:
kdDebug(KSSHPROC) << "KSshProcess::connect(): "
"Invalid state number - " << mConnectState << endl;
mError = ERR_INVALID_STATE;
mConnectState = STATE_FATAL;
}
}
// we should never get here
kdDebug(KSSHPROC) << "KSshProcess::connect(): " <<
"After switch(). We shouldn't be here." << endl;
mError = ERR_INTERNAL;
return false;
}
void KSshProcess::disconnect() {
kill();
mConnected = false;
mRunning = false;
mConnectState = STATE_START;
}