Search code examples
sftpparamiko

Sftp paramiko and sshd-mina not able to write on remote server even if no error. On local sftp server it works though


I'm just showing the code used to try to upload a file in sftp:

# Check file size
def check_file_size(path):
    try:
        size = os.path.getsize(path)
        print(f"Local file size: {size} bytes")
        return size
    except OSError as e:
        print(f"Failed to get size of {path}: {e}")
        return None

# Create an SFTP session
def create_sftp_session(hostname, port, username, password):
    transport = paramiko.Transport((hostname, port))
    transport.connect(username=username, password=password)
    sftp = paramiko.SFTPClient.from_transport(transport)
    return sftp, transport

# Upload a file to the remote server, overwriting if it exists
def upload_file(sftp, local_path, remote_path, retries=3):
        try:
            # Create a temporary file and copy content to it
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                with open(local_path, 'r') as file:
                    temp_file.write(file.read().encode())
                temp_file_path = temp_file.name
            
            local_size = os.path.getsize(temp_file_path)
            sftp.put(temp_file_path, remote_path)
            remote_size = sftp.stat(remote_path).st_size
            print(f"Remote file size after upload: {remote_size} bytes")
            if local_size != remote_size:
                print(f"Warning: Size mismatch after upload!")
            
            # Clean up temporary file
            os.remove(temp_file_path)
        except IOError as e:
            print(f"Failed to upload {local_path} to {remote_path}: {e}")

# Delete the local file
def delete_local_file(path):
    try:
        os.remove(path)
        print(f"Local file {path} deleted.")
    except OSError as e:
        print(f"Failed to delete {path}: {e}")

# Main execution
def main():
    # Create the local file
    create_local_file(local_file_path)

    # Set up SFTP session and upload file
    sftp, transport = create_sftp_session(hostname, port, username, password)
    try:
        # Upload and overwrite the file
        upload_file(sftp, local_file_path, remote_file_path)
    finally:
        sftp.close()
        transport.close()

    # Delete the local file
    delete_local_file(local_file_path)

if __name__ == "__main__":
    main()

If run against a remote server it fails:

Failed to upload localfile.txt to /dir/removefile.csv: size mismatch in put! 94 != 141

I run the same code against a local sftp server with docker and it works. Same for the code using sshd-mina (not shown here). This is the unexpected. Why it doesn't work against the remote server?

Another thing that I noticed is that, if I change the upload_file function as:

# Upload a file to the remote server, overwriting if it exists
def upload_file(sftp, local_path, remote_path, retries=3):
    for attempt in range(retries):
        try:
            # Create a temporary file and copy content to it
            with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                with open(local_path, 'r') as file:
                    temp_file.write(file.read().encode())
                temp_file_path = temp_file.name
            
            local_size = os.path.getsize(temp_file_path)
            sftp.put(temp_file_path, remote_path)
            remote_size = sftp.stat(remote_path).st_size
            print(f"Remote file size after upload: {remote_size} bytes")
            if local_size != remote_size:
                print(f"Warning: Size mismatch after upload!")
            
            # Clean up temporary file
            os.remove(temp_file_path)
            break  # Exit loop if upload is successful
        except IOError as e:
            print(f"Attempt {attempt + 1} failed to upload {local_path} to {remote_path}: {e}")
            if attempt < retries - 1:
                print("Retrying...")
                time.sleep(5)  # Wait before retrying

It works at the second attempt! (the change consists in just retrying if an exception is raised).

Local file localfile.txt created with content. Attempt 1 failed to upload localfile.txt to /flex-selections/Portfolio.csv: size mismatch in put! 94 != 141 Retrying... Remote file size after upload: 141 bytes Local file localfile.txt deleted.

How is this possible?


Solution

  • The problem was on server side. The legacy S/FTP frontdoor used does not fully support all SFTP commands/features (It's a FTP server with a SFTP enhancement.).

    The S/FTP frontdoor does not properly process the target directory from the "put" method and does not forward a "CWD" command to the FTP server.

    Workaround: Adding "chdir()" or "listdir()" before the "put()" forces a "CWD" and solved the problem. This is what I think Filezilla does.