summaryrefslogtreecommitdiffstats
path: root/kioslave/sftp/ksshprocess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/sftp/ksshprocess.cpp')
-rw-r--r--kioslave/sftp/ksshprocess.cpp1114
1 files changed, 0 insertions, 1114 deletions
diff --git a/kioslave/sftp/ksshprocess.cpp b/kioslave/sftp/ksshprocess.cpp
deleted file mode 100644
index 3393f8934..000000000
--- a/kioslave/sftp/ksshprocess.cpp
+++ /dev/null
@@ -1,1114 +0,0 @@
-/***************************************************************************
- 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 tdelibs 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::fromLatin1("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.find(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::fromLatin1(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.contains("NumberOfPasswordPrompts") ||
- tmp.contains("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.
- // Hack around select() failure on newer systems
- unsigned long milliseconds = 0;
- while ((buffer.size() == 0) && (milliseconds < (60*1000))) {
- //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);
- if (ptyLine.size() > 0) {
- buffer.prepend(TQString(ptyLine));
- }
- //kdDebug(KSSHPROC) << "KSshProcess::getLine(): "
- // "line from pty -" << ptyLine << endl;
- }
-
- if( FD_ISSET(errfd, &rfds) ) {
- errLine = ssh.readLineFromStderr(false);
- if (errLine.size() > 0) {
- 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;
- }
-
- if (buffer.size() == 0) {
- milliseconds++;
- usleep(1000);
- }
- }
- }
-
- // 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.find(TQString::fromLatin1(passwordPrompt[mVersion]), 0, false) != -1 ) {
- mConnectState = STATE_TRY_PASSWD;
- }
- else if( line.find(passphrasePrompt[mVersion]) != -1 ) {
- mConnectState = STATE_TRY_PASSPHRASE;
- }
- else if( line.find(authSuccessMsg[mVersion]) != -1 ) {
- return true;
- }
- else if( line.find(authFailedMsg[mVersion]) != -1
- && line.find(tryAgainMsg[mVersion]) == -1 ) {
- mConnectState = STATE_AUTH_FAILED;
- }
- else if( line.find(hostKeyMissingMsg[mVersion]) != -1 ) {
- mConnectState = STATE_NEW_KEY_WAIT_CONTINUE;
- }
- else if( line.find(hostKeyChangedMsg[mVersion]) != -1 ) {
- mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE;
- }
- else if( line.find(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.find(connectionClosedMsg[mVersion]) != -1 ) {
- mConnectState = STATE_FATAL;
- mError = ERR_CLOSED_BY_REMOTE_HOST;
- mErrorMsg = i18n("Connection closed by remote host.");
- }
- else if( line.find(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.find(authFailedMsg[mVersion]) != -1
- && line.find(tryAgainMsg[mVersion]) == -1)
- || line.find(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.find(continuePrompt[mVersion]) != -1 ) {
- mConnectState = STATE_NEW_KEY_CONTINUE;
- }
- else if( line.find(connectionClosedMsg[mVersion]) != -1 ) {
- mConnectState = STATE_FATAL;
- mError = ERR_CLOSED_BY_REMOTE_HOST;
- mErrorMsg = i18n("Connection closed by remote host.");
- }
- else if( line.find(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.find(authFailedMsg[mVersion]) != -1
- && line.find(tryAgainMsg[mVersion]) == -1)
- || line.find(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.find(continuePrompt[mVersion]) != -1 ) {
- mConnectState = STATE_DIFF_KEY_CONTINUE;
- }
- else if( line.find(keyFingerprintMsg[mVersion]) != -1 ) {
- mKeyFingerprint = keyFingerprintMsg[mVersion].cap();
- kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl;
- mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE;
- }
- else if( line.find(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;
-}
-