"'Connection' object has no attribute '_sftp_live'" when pysftp connection fails
Asked Answered
T

3

18

I'd like to catch nicely the error when "No hostkey for host *** is found" and give an appropriate message to the end user. I tried this:

import pysftp, paramiko
try: 
    with pysftp.Connection('1.2.3.4', username='root', password='') as sftp:
        sftp.listdir()
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)

but unfortunately the output is not very nice:

SSH error, you need to add the public key of your remote in your local known_hosts file first. No hostkey for host 1.2.3.4 found.
Exception ignored in: <function Connection.__del__ at 0x00000000036B6D38>
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\pysftp\__init__.py", line 1013, in __del__
    self.close()
  File "C:\Python37\lib\site-packages\pysftp\__init__.py", line 784, in close
    if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'

How to nicely avoid these last lines / this "exception ignored" with a try: except:?

Thar answered 25/11, 2020 at 10:15 Comment(1)
As of 2022, the pysftp seems to be an abandoned project. I suggest you switch to Paramiko. The pysftp is just a wrapper around Paramiko. So if you have pysftp, you have Paramiko too. See pysftp vs. Paramiko.Winkler
E
12

The analysis by @reverse_engineer is correct. However:

  1. It seems that an additional attribute, self._transport, also is defined too late.
  2. The problem can be temporarily corrected until a permanent fix comes by subclassing the pysftp.Connection class as follows:
import pysftp
import paramiko


class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

try: 
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)

Update

I could not duplicate this error on my desktop. However, I see in the source for pysftp in the code where it initializes its _cnopts attribute with self._cnopts = cnopts or CnOpts() where cnopts is a keyword parameter to the pysftp.Connection constructor and there is a possibilty of the CnOpts constructor throwing a HostKeysException exception if no host keys are found resulting in the _cnopts attribute not being set.

Try the following updated code and let me know if it works:

import pysftp
import paramiko

class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        try:
            if kwargs.get('cnopts') is None:
                kwargs['cnopts'] = pysftp.CnOpts()
        except pysftp.HostKeysException as e:
            self._init_error = True
            raise paramiko.ssh_exception.SSHException(str(e))
        else:
            self._init_error = False

        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

    def __del__(self):
        if not self._init_error:
            self.close()

try:
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)
Enstatite answered 29/11, 2020 at 12:41 Comment(3)
Nicely found, though these kind of fixes can be a bit risky since in the end you change the behavior of the constructor, but I suspects this works without side-effectsGuipure
I tried the above code with my credentials but getting the below error >>> AttributeError: 'MyConnection' object has no attribute '_cnopts'Bogle
@Bogle If that doesn't resolve your issue, open a new question and add a comment directed to me so that I know you have done so. But be sure to post a full stacktrace.Enstatite
B
18

I had the same problem. I solved this by disabling the hostkeys in the cnops:

import pysftp as sftp

FTP_HOST = "sftp.abcd.com"
FTP_USER = "root"
FTP_PASS = ""

cnopts = sftp.CnOpts()
cnopts.hostkeys = None

with sftp.Connection(host=FTP_HOST, username=FTP_USER, password=FTP_PASS, cnopts=cnopts) as sftp:
   print("Connection succesfully stablished ... ")
   sftp.cwd('/folder/')  # Switch to a remote directory
   directory_structure = sftp.listdir_attr() # Obtain structure of the remote directory

for attr in directory_structure:
   print(attr.filename, attr)
Buenrostro answered 3/1, 2022 at 8:19 Comment(1)
Disabling host key checking is a security flaw. You lose a protection against MITM attacks. Do not do that!Winkler
G
12

I think that's a bug in pysftp. You will always have that behavior when pysftp.Connection fails on a No hostkey for XXX found exception, because the failed Connection object (it fails so you can't access it, but it exists in the Python interpreter) gets cleaned up by the GC, which deletes it, and as you can see here, that tries to close the connection first.

We see that close() checks whether the connection is live by checking self._sftp_live. However, the Exception was thrown in the constructor of Connection before that attribute is defined (the exception happens line 132, while _sftp_live is defined line 134), so that leaves the failed Connection object in an inconsistent state and hence the uncaught exception you see.

This has no easy solution that I can think of except introducing a nice bug fix to the pysftp project ;)

Guipure answered 29/11, 2020 at 11:41 Comment(2)
I believe this bug still exists for anyone coming to this answer in Sept 2021 - just got burned by this badly. When you hit this in durasftp, since on closure it tries to reconnect, you will see this log millions of times.Vinery
i still do have the same problem using pysftp 0.2.9Freemanfreemartin
E
12

The analysis by @reverse_engineer is correct. However:

  1. It seems that an additional attribute, self._transport, also is defined too late.
  2. The problem can be temporarily corrected until a permanent fix comes by subclassing the pysftp.Connection class as follows:
import pysftp
import paramiko


class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

try: 
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)

Update

I could not duplicate this error on my desktop. However, I see in the source for pysftp in the code where it initializes its _cnopts attribute with self._cnopts = cnopts or CnOpts() where cnopts is a keyword parameter to the pysftp.Connection constructor and there is a possibilty of the CnOpts constructor throwing a HostKeysException exception if no host keys are found resulting in the _cnopts attribute not being set.

Try the following updated code and let me know if it works:

import pysftp
import paramiko

class My_Connection(pysftp.Connection):
    def __init__(self, *args, **kwargs):
        try:
            if kwargs.get('cnopts') is None:
                kwargs['cnopts'] = pysftp.CnOpts()
        except pysftp.HostKeysException as e:
            self._init_error = True
            raise paramiko.ssh_exception.SSHException(str(e))
        else:
            self._init_error = False

        self._sftp_live = False
        self._transport = None
        super().__init__(*args, **kwargs)

    def __del__(self):
        if not self._init_error:
            self.close()

try:
    with My_Connection('1.2.3.4', username='root', password='') as sftp:
        l = sftp.listdir()
        print(l)
except paramiko.ssh_exception.SSHException as e:
    print('SSH error, you need to add the public key of your remote in your local known_hosts file first.', e)
Enstatite answered 29/11, 2020 at 12:41 Comment(3)
Nicely found, though these kind of fixes can be a bit risky since in the end you change the behavior of the constructor, but I suspects this works without side-effectsGuipure
I tried the above code with my credentials but getting the below error >>> AttributeError: 'MyConnection' object has no attribute '_cnopts'Bogle
@Bogle If that doesn't resolve your issue, open a new question and add a comment directed to me so that I know you have done so. But be sure to post a full stacktrace.Enstatite

© 2022 - 2024 — McMap. All rights reserved.