Table of Contents

Transfer files with SCP

using SshSharp.Scp; adds SCP transfers to SshClient as extension methods. They run the server's scp program over an exec channel (scp -t to receive, scp -f to send), as the classic scp command does.

When to use

  • The server has no SFTP subsystem: some embedded systems, appliances and restricted shells offer only scp.
  • Otherwise prefer SFTP: it can list, resume and change files, and does not depend on a program on the server.

Files

// using SshSharp.Scp;
await client.UploadFileAsync("report.csv", "/srv/reports/report.csv", cancellationToken: cancellationToken);
await client.DownloadFileAsync("/var/log/app.log", "app.log", cancellationToken: cancellationToken);

// An existing directory as the target keeps the other side's file name, as scp does.
await client.UploadFileAsync("report.csv", "/srv/reports/", cancellationToken: cancellationToken);

Streams

var json = Encoding.UTF8.GetBytes("""{ "feature": true }""");
using (var source = new MemoryStream(json))
{
    await client.UploadAsync(source, "/etc/myapp/flags.json", cancellationToken: cancellationToken); // the length comes from the stream
}

using var destination = new MemoryStream();
await client.DownloadAsync("/etc/hostname", destination, cancellationToken: cancellationToken);

UploadAsync takes the length from the stream; for a stream that cannot seek, pass length:. SCP sends the length before the data, so it must be known in advance.

Directories, permissions and progress

var options = new SshScpOptions
{
    Preserve = true,                                            // scp -p
    FileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite,  // 0600 for files whose mode is not preserved
    Progress = new Progress<SshScpProgress>(p => Console.Write($"\r{p.Path}: {p.BytesTransferred:N0}/{p.TotalBytes:N0}")),
};
await client.UploadDirectoryAsync("site", "/var/www/site", options, cancellationToken);   // scp -r
await client.DownloadDirectoryAsync("/var/log/app", "logs", options, cancellationToken);

As with scp -r, the directory becomes the target when that does not exist, or goes inside it when it does.

Notes and pitfalls

  • Errors. A failed transfer (missing file, permission denied, no scp on the server) throws SshException with SshFailureReason.TransferFailed and the server's own message.
  • Quoting. Remote paths are passed to the server's shell as one single-quoted word, which is safe with POSIX shells. A Windows server whose default shell is cmd.exe or PowerShell does not read single quotes that way.
  • Hostile servers. Every name the server sends must be one path component, and a single-file download accepts exactly one file, so a server cannot write outside the target or add files (CVE-2019-6111). Set-id and sticky bits are never applied.
  • No resume, no listing. SCP has neither; use SFTP for those.
  • ScpCommand. SshScpOptions.ScpCommand names the program to run on the server, when scp is not on its PATH.

See also