14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
"""Foundation SSH support for SFTP and smart server."""
122
123
elif 'SSH Secure Shell' in version:
123
124
trace.mutter('ssh implementation is SSH Corp.')
124
125
vendor = SSHCorpSubprocessVendor()
125
elif 'plink' in version and args[0] == 'plink':
126
# Checking if "plink" was the executed argument as Windows
127
# sometimes reports 'ssh -V' incorrectly with 'plink' in it's
128
# version. See https://bugs.launchpad.net/bzr/+bug/107155
129
trace.mutter("ssh implementation is Putty's plink.")
130
vendor = PLinkSubprocessVendor()
126
# Auto-detect of plink vendor disabled, on Windows recommended
127
# default ssh-client is paramiko
128
# see https://bugs.launchpad.net/bugs/414743
129
#~elif 'plink' in version and args[0] == 'plink':
130
#~ # Checking if "plink" was the executed argument as Windows
131
#~ # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
132
#~ # version. See https://bugs.launchpad.net/bzr/+bug/107155
133
#~ trace.mutter("ssh implementation is Putty's plink.")
134
#~ vendor = PLinkSubprocessVendor()
133
137
def _get_vendor_by_inspection(self):
212
216
def connect_sftp(self, username, password, host, port):
213
217
"""Make an SSH connection, and return an SFTPClient.
215
219
:param username: an ascii string
216
220
:param password: an ascii string
217
221
:param host: a host name as an ascii string
227
231
def connect_ssh(self, username, password, host, port, command):
228
232
"""Make an SSH connection.
230
234
:returns: something with a `close` method, and a `get_filelike_channels`
231
235
method that returns a pair of (read, write) filelike objects.
391
395
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
393
397
"""Returns the argument list to run the subprocess with.
395
399
Exactly one of 'subsystem' and 'command' must be specified.
397
401
raise NotImplementedError(self._get_vendor_specific_argv)
460
464
def _paramiko_auth(username, password, host, port, paramiko_transport):
461
# paramiko requires a username, but it might be none if nothing was supplied
462
# use the local username, just in case.
463
# We don't override username, because if we aren't using paramiko,
464
# the username might be specified in ~/.ssh/config and we don't want to
465
# force it to something else
466
# Also, it would mess up the self.relpath() functionality
467
465
auth = config.AuthenticationConfig()
466
# paramiko requires a username, but it might be none if nothing was
467
# supplied. If so, use the local username.
468
468
if username is None:
469
username = auth.get_user('ssh', host, port=port)
471
# Default to local user
472
username = getpass.getuser()
469
username = auth.get_user('ssh', host, port=port,
470
default=getpass.getuser())
474
471
if _use_ssh_agent:
475
472
agent = paramiko.Agent()
476
473
for key in agent.get_keys():
488
485
if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
488
# If we have gotten this far, we are about to try for passwords, do an
489
# auth_none check to see if it is even supported.
490
supported_auth_types = []
492
# Note that with paramiko <1.7.5 this logs an INFO message:
493
# Authentication type (none) not permitted.
494
# So we explicitly disable the logging level for this action
495
old_level = paramiko_transport.logger.level
496
paramiko_transport.logger.setLevel(logging.WARNING)
498
paramiko_transport.auth_none(username)
500
paramiko_transport.logger.setLevel(old_level)
501
except paramiko.BadAuthenticationType, e:
502
# Supported methods are in the exception
503
supported_auth_types = e.allowed_types
504
except paramiko.SSHException, e:
505
# Don't know what happened, but just ignore it
507
# We treat 'keyboard-interactive' and 'password' auth methods identically,
508
# because Paramiko's auth_password method will automatically try
509
# 'keyboard-interactive' auth (using the password as the response) if
510
# 'password' auth is not available. Apparently some Debian and Gentoo
511
# OpenSSH servers require this.
512
# XXX: It's possible for a server to require keyboard-interactive auth that
513
# requires something other than a single password, but we currently don't
515
if ('password' not in supported_auth_types and
516
'keyboard-interactive' not in supported_auth_types):
517
raise errors.ConnectionError('Unable to authenticate to SSH host as'
518
'\n %s@%s\nsupported auth types: %s'
519
% (username, host, supported_auth_types))
493
523
paramiko_transport.auth_password(username, password)
498
528
# give up and ask for a password
499
529
password = auth.get_password('ssh', host, username, port=port)
501
paramiko_transport.auth_password(username, password)
502
except paramiko.SSHException, e:
503
raise errors.ConnectionError(
504
'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
530
# get_password can still return None, which means we should not prompt
531
if password is not None:
533
paramiko_transport.auth_password(username, password)
534
except paramiko.SSHException, e:
535
raise errors.ConnectionError(
536
'Unable to authenticate to SSH host as'
537
'\n %s@%s\n' % (username, host), e)
539
raise errors.ConnectionError('Unable to authenticate to SSH host as'
540
' %s@%s' % (username, host))
507
543
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
569
605
def os_specific_subprocess_params():
570
606
"""Get O/S specific subprocess parameters."""
571
607
if sys.platform == 'win32':
572
# setting the process group and closing fds is not supported on
608
# setting the process group and closing fds is not supported on
576
# We close fds other than the pipes as the child process does not need
612
# We close fds other than the pipes as the child process does not need
577
613
# them to be open.
579
615
# We also set the child process to ignore SIGINT. Normally the signal
581
617
# this causes it to be seen only by bzr and not by ssh. Python will
582
618
# generate a KeyboardInterrupt in bzr, and we will then have a chance
583
619
# to release locks or do other cleanup over ssh before the connection
585
621
# <https://launchpad.net/products/bzr/+bug/5987>
587
623
# Running it in a separate process group is not good because then it