Skip to content

Add file copies - #48

Merged
bradfitz merged 7 commits into
mainfrom
bradfitz/files
Aug 31, 2026
Merged

Add file copies#48
bradfitz merged 7 commits into
mainfrom
bradfitz/files

Conversation

@bradfitz

@bradfitz bradfitz commented Aug 31, 2026

Copy link
Copy Markdown
Member

We already had an SSH server, so might as well add the sftp support in there too, along with some ergonomic wrappers around sftp. ("cp", "ls")

Updates #37

The SSH server registered no sftp subsystem handler, so sftp and
modern scp (which speaks SFTP since OpenSSH 9.0) failed against it,
and moving files meant piping tar through an exec session.

Shell-enabled SSH sessions now also serve SFTP with the same access
the shell has, like OpenSSH. Alongside that, SSHConnHandler accepts
options for a rooted file service: an os.Root-confined directory
(neither ".." nor symlinks escape it) served read-only, read-write,
or write-only. Write-only is a drop box: uploads and mkdirs work,
but nothing can be listed or read back, and only paths a session
wrote itself may be stat'd (plus directories, so destinations
resolve), so filenames can't be probed. The CLI wiring for the
rooted mode comes separately.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If7269f501975c08930fe5ccd7ea78870fb277328
Add a 'files' service to serve that runs the rooted SFTP file
service over the SSH transport: "tailcat serve files" serves the
current directory read-only to scp and sftp clients. The --files
flag picks another directory and access mode (:ro, :rw, or :wo for
a write-only drop box) and implies the service, without enabling
shell sessions unless no-auth-ssh is also requested.

A server running only named services previously still behaved as the
empty-port-list accept-one-connection stdout mode on any stray port:
a connection to, say, port 80 of a no-auth-ssh server dumped to
stdout and exited the process. Named services now suppress that mode
and tighten the packet filter to the served ports.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I2ef1850062779ba68856d014aa6d89108f3eb434
The sftp subsystem handler closed the SFTP server (and with it the
session channel) via a defer that ran before sess.Exit, so the
exit-status message was sent on a closed channel and lost. OpenSSH
scp treats a session that ends without an exit-status as a failure
(Exit status -1), reporting failure for transfers that succeeded.

Exit first, then close the server to release any handles a client
left open.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I4c0884df2f92999d758e678997d70aa62e125bc6
tailcat cp copies files to or from a tailcat server with scp syntax:
remote paths are <addrblob>:path (or a DNS name with a tailcat= TXT
record). Like tailcat ssh, it execs the system client (scp here)
with a ProxyCommand that routes the connection through tailcat, so
scp's progress display and recursive copies come along for free.
The natural server side is "tailcat serve files", though a full
no-auth-ssh server works too.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I2f5a74471c1db8f27cda4fb9d80f91a896f9d7e1
tailcat recv serves a directory (default: the current one) as a
write-only drop box and prints the address blob, so receiving a file
is one command on each side:

	receiver$ tailcat recv
	sender$   tailcat cp foo.txt <addrblob>:

It's shorthand for "tailcat serve --files=<dir>:wo files": senders
can't list the directory, read anything back, or touch existing
files.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I65e7e03adc2bbc752454ba210feb46e4353716e0
Cover tailcat recv, tailcat cp, and the serve files service, and how
they compose with stock OpenSSH sftp/scp clients.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I73b1e5061280c43996c2dc3197f649f3c35a0fd6
tailcat ls [-l] <addrblob>[:path] lists what a file server offers.
Unlike ssh and cp, which exec OpenSSH binaries, ls speaks SFTP
natively (x/crypto/ssh and pkg/sftp are already dependencies), so it
works with no OpenSSH installed, and its output is one clean name
per line rather than sftp batch-mode chatter.

Updates #37

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I942f01707fb025524f7acb1fd60324a947130254
@bradfitz
bradfitz merged commit 3748bfd into main Aug 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant