/*************************************************************************** ksshprocess.h - 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. * * * ***************************************************************************/ #ifndef KSSHPROCESS_H #define KSSHPROCESS_H #include #include #include #include #include #include #include "process.h" #define KSSHPROC 7120 /** * Provides version independent access to ssh. Currently supported * versions of SSH are: * OpenSSH 2.9p1 * OpenSSH 2.9p2 * OpenSSH 3.0 * OpenSSH 3.1 * Commercial SSH 3.0.0 * Other versions of OpenSSH and commerical SSH will probably work also. * * To setup a SSH connection first create a list of options to use and tell * KSshProcess about your options. Then start the ssh connection. Once the * connection is setup use the stdin, stdout, stderr, and pty file descriptors * to communicate with ssh. For a detailed example of how to use, see * ksshprocesstest.cpp. * * @author Lucas Fisher * * Example: Connect to ssh server on localhost * KSshProcess::SshOpt opt; * KSshProcess::SshOptList options; * * opt.opt = KSshProcess::SSH_HOST; * opt.str = "localhost"; * options.append(opt); * * opt.opt = KSshProcess::SSH_USERNAME; * opt.str = "me"; * options.append(opt); * * KSshProcess ssh; * if( !ssh.setOptions(options) ) { * int err = ssh.error(); * // process error * return false; * } * * int err; * QString errMsg; * while( !ssh.connect() ) { * err = ssh.error(errMsg); * * switch( err ) { * case KSshProcess::ERR_NEW_HOST_KEY: * case KSshProcess::ERR_DIFF_HOST_KEY: * // ask user to accept key * if( acceptHostKey ) { * ssh.acceptKey(true); * } * break; * * case KSshProcess::ERR_NEED_PASSWORD: * // ask user for password * ssh.password(userPassword); * break; * * case KSshProcess::ERR_NEED_KEY_PASSPHRASE: * // ask user for their key passphrase * ssh.keyPassphrase(keyPassphrase); * break; * * default: * // somethings wrong, alert user * return; * } * } * // We have an open ssh connection to localhost * */ class KSshProcess { public: /** * SSH Option * * Stores SSH options for use with KSshProcess. * * SSH options are configured much like UDS entries. * Each option is assigned a constant and a string, bool, * or number is assigned based on the option. * * @author Lucas Fisher (ljfisher@iastate.edu) */ class SshOpt { public: Q_UINT32 opt; QString str; Q_INT32 num; bool boolean; }; /** * List of SshOptions and associated iterators */ typedef QValueList SshOptList; typedef QValueListIterator SshOptListIterator; typedef QValueListConstIterator SshOptListConstIterator; /** * Ssh versions supported by KSshProcess. Subject to change * at any time. */ enum SshVersion { OPENSSH_3_6, OPENSSH, SSH, SSH_VER_MAX, UNKNOWN_VER }; /** * SSH options supported by KSshProcess. Set SshOpt::opt to one of these * values. */ // we cannot do this like UDSAtomType (ORing the type with the name) because // we have too many options for ssh and not enough bits. enum SshOptType { /** * Request server to invoke subsystem. (str) */ SSH_SUBSYSTEM, /** * Connect to port on the server. (num) */ SSH_PORT, /** * Connect to host. (str) */ SSH_HOST, /** * connect using this username. (str) */ SSH_USERNAME, /** * connect using this password. (str) */ SSH_PASSWD, /** * connect using this version of the SSH protocol. num == 1 or 2 */ SSH_PROTOCOL, /** * whether to forward X11 connections. (boolean) */ SSH_FORWARDX11, /** * whether to do agent forwarding. (boolean) */ SSH_FORWARDAGENT, /** * use as escape character. 0 for none (num) */ SSH_ESCAPE_CHAR, /** * command for ssh to perform once it is connected (str) */ SSH_COMMAND, /** * Set ssh verbosity. This may be added multiple times. It may also cause KSSHProcess * to fail since we don't understand all the debug messages. */ SSH_VERBOSE, /** * Set a ssh option as one would find in the ssh_config file * The str member should be set to 'optName value' */ SSH_OPTION, /** * Set some other option not supported by KSSHProcess. The option should * be specified in the str member of SshOpt. Careful with this since * not all versions of SSH support the same options. */ SSH_OTHER, SSH_OPT_MAX // always last }; // that's all for now /** * Errors that KSshProcess can encounter. When a member function returns * false, call error() to retrieve one of these error codes. */ enum SshError { /** * Don't recognize the ssh version */ ERR_UNKNOWN_VERSION, /** * Cannot lauch ssh client */ ERR_CANNOT_LAUNCH, /** * Interaction with the ssh client failed. This happens when we can't * find the password prompt or something similar */ ERR_INTERACT, /** * Arguments for both a remotely executed subsystem and command were provide. * Only one or the other may be used */ ERR_CMD_SUBSYS_CONFLICT, /** * No password was supplied */ ERR_NEED_PASSWD, /** * No passphrase was supplied. */ ERR_NEED_PASSPHRASE, /** * No usename was supplied */ ERR_NEED_USERNAME, /** * Timed out waiting for a response from ssh or the server */ ERR_TIMED_OUT, /** * Internal error, probably from a system call */ ERR_INTERNAL, /** * ssh was disconnect from the host */ ERR_DISCONNECTED, /** * No ssh options have been set. Call setArgs() before calling connect. */ ERR_NO_OPTIONS, /** * A host key was received from an unknown host. * Call connect() with the acceptHostKey argument to accept the key. */ ERR_NEW_HOST_KEY, /** * A host key different from what is stored in the user's known_hosts file * has be received. This is an indication of an attack */ ERR_DIFF_HOST_KEY, /** * A new or different host key was rejected by the caller. The ssh * connection was terminated and the ssh process killed. */ ERR_HOST_KEY_REJECTED, /** * An invalid option was found in the SSH option list */ ERR_INVALID_OPT, /** * SSH accepted host key without prompting user. */ ERR_ACCEPTED_KEY, /** * Authentication failed */ ERR_AUTH_FAILED, /** * Authentication failed because a new host key was detected and * SSH is configured with strict host key checking enabled. */ ERR_AUTH_FAILED_NEW_KEY, /** * Authentication failed because a changed host key was detected and * SSH is configured with strict host key checking enabled. */ ERR_AUTH_FAILED_DIFF_KEY, /** * The remote host closed the connection for unknown reasons. */ ERR_CLOSED_BY_REMOTE_HOST, /** * We have no idea what happened */ ERR_UNKNOWN, /** * The connect state machine entered an invalid state. */ ERR_INVALID_STATE, ERR_MAX }; /** * Initialize a SSH process using the first SSH binary found in the PATH */ KSshProcess(); /** * Initialize a SSH process using the specified SSH binary. * @param pathToSsh The fully qualified path name of the ssh binary * KSshProcess should use to setup a SSH connection. */ KSshProcess(QString pathToSsh); ~KSshProcess(); /** * Set the ssh binary KSshProcess should use. This will only affect the * next ssh connection attempt using this instance. * * @param pathToSsh Full path to the ssh binary. * * @return True if the ssh binary is found and KSshProcess * recognizes the version. * */ bool setSshPath(QString pathToSsh); /** * Get the ssh version. * * @return The ssh version or -1 if KSshProcess does not recognize * the ssh version. The returned value corresponds to the * member of the SshVersion enum. */ SshVersion version(); /** * Get a string describing the ssh version * * @return A string describing the ssh version recognized by KSshProcess */ //QString versionStr(); /** * Get the last error encountered by KSshProcess. * * @param msg Set to the error message, if any, outputted by ssh when it is run. * * @return The error number. See SshError for descriptions. */ int error(QString& msg); /** * Get the last error encountered by KSshProcess. * @return The error number. See SshError for descriptions. */ int error() { return mError; } QString errorMsg() { return mErrorMsg; } /** * Send a signal to the ssh process. Do not use this to end the * ssh connection as it will not correctly reset the internal * state of the KSshProcess object. Use KSshProcess::disconnect() * instead. * * @param signal The signal to send to the ssh process. See 'kill -l' * for a list of possible signals. * The default signal is SIGKILL which kills ssh. * */ void kill(int signal = SIGKILL); /** * The pid of the ssh process started by this instance of KSshProcess. * Only valid if KSshProcess::running() returns true; * * @return The pid of the running ssh process. */ int pid() { return ssh.pid(); } /** * Whether a ssh connection has been established with a * remote host. A establish connection means ssh has successfully * authenticated with the remote host and user data can be transfered * between the local and remote host. This cannot return * true unless the most recent call to KSshProccess::connect() returned true. * * @return True if a ssh connection has been established with a remote * host. False otherwise. */ bool connected() { return mConnected; } /** * Whether a ssh process is currently running. This only indicates * if a ssh process has been started and is still running. It does not * tell if authentication has been successful. This may return true * even if the most recent call to KSshProcess::connect() returned false. * * @return True if a ssh process started by this instance of KSshProcess * is running. False otherwise. */ bool running() { return mRunning; } /** * Print the command line arguments ssh is run with using kdDebug. */ void printArgs(); /** * Set the SSH options. * This must be called before connect(). See SshOptType for a list of * supported ssh options. The required options are SSH_USERNAME * and SSH_HOST. * * To reset the saved options, just recall setOptions() again with * a different options list. * * @param opts A list of SshOpt objects specifying the ssh options. * * @return True if all options are valid. False if unrecognized options * or a required option is missing. Call error() * for details. * */ bool setOptions(const SshOptList& opts); /** * Create a ssh connection based on the options provided by setOptions(). * Sets one of the following error codes on failure: *
    *
  • ERR_NO_OPTIONS
  • *
  • ERR_CANNOT_LAUNCH
  • *
  • ERR_INVALID_STATE
  • *
  • ERR_NEED_PASSWD
  • *
  • ERR_AUTH_FAILED
  • *
  • ERR_NEW_HOST_KEY
  • *
  • ERR_KEY_ACCEPTED
  • *
  • ERR_DIFF_HOST_KEY
  • *
  • ERR_INTERNAL
  • *
  • ERR_INTERACT
  • *
* * @param acceptHostKey When true KSshProcess will automatically accept * unrecognized or changed host keys. * * @return True if the ssh connection is successful. False if the connection * fails. Call error() to get the reason for the failure. */ bool connect(); /** * Disconnect ssh from the host. This kills the ssh process and * resets the internal state of this KSshProcess object. After a * disconnect, the same KSshProcess can be used to connect to a * host. */ void disconnect(); /** * Call to respond to a ERR_NEW_HOST_KEY or ERR_DIFF_HOST_KEY error. * * @param accept True to accept the host key, false to not accept the * host key and kill ssh. * */ void acceptHostKey(bool accept); /** * Call to respond to a ERR_NEED_PASSWD or ERR_NEED_PASSPHRASE error. * * @param password The user password to give ssh. */ void setPassword(QString password); /** * Access to standard in and out of the ssh process. * * @return The file description for stdin and stdout of the ssh process. */ int stdioFd() { return ssh.stdioFd(); } /** * Access to standard error of the ssh process. * * @return The file descriptior for stderr of the ssh process. */ int stderrFd() { return ssh.stderrFd(); } /** * Access the pty to which the ssh process is attached. * * @return The file descriptor of pty to which ssh is attached. */ int pty() { return ssh.fd(); } private: /** * Path the the ssh binary. */ QString mSshPath; /** * SSH version. This is an index into the supported SSH * versions array, and the various messages arrays. */ SshVersion mVersion; /** * User's password. Zero this out when it is no longer needed. */ QString mPassword; /** * User's username. */ QString mUsername; /** * Name of host we are connecting to. */ QString mHost; /** * Accept new or changed host keys if true. */ bool mAcceptHostKey; /** * Flag to tell use if we have an open, authenticated ssh * session going. */ bool mConnected; /** * Flag to tell us if we have started a ssh process, we use this * to make sure we kill ssh before going away. */ bool mRunning; /** * Save any key fingerprint msg from ssh so we can present * it to the caller. */ QString mKeyFingerprint; /** * The location of the known host key file. We grab this from * any error messages ssh prints out. */ QString mKnownHostsFile; /** * The state of our connect state machine. */ int mConnectState; /** * Port on on which the target ssh server is listening. */ int mPort; /** * The last error number encountered. This is only valid for the * last error. */ SshError mError; /** * An error message that corresponds to the error number set in * mError. Optional. */ QString mErrorMsg; /** * Interface to the SSH process we ceate. Handles communication * to and from the SSH process using stdin, stdout, stderr, and * pty. */ MyPtyProcess ssh; /** * List of arguments we start SSH with. */ QCStringList mArgs; void init(); /** * Handler to clean up when ssh process terminates. */ static void SIGCHLD_handler(int signo); void installSignalHandlers(); void removeSignalHandlers(); QString getLine(); static QRegExp versionStrs[]; static const char * const passwordPrompt[]; static const char * const passphrasePrompt[]; static const char * const authSuccessMsg[]; static const char * const authFailedMsg[]; static QRegExp hostKeyMissingMsg[]; static const char * const hostKeyChangedMsg[]; static const char * const continuePrompt[]; static const char * const hostKeyAcceptedMsg[]; static const char * const tryAgainMsg[]; static QRegExp hostKeyVerifyFailedMsg[]; static const char * const connectionClosedMsg[]; static const char * const changeHostKeyOnDiskPrompt[]; static QRegExp keyFingerprintMsg[]; static QRegExp knownHostsFileMsg[]; }; #endif