unpack tar files copied via sftp subsystem, too (not just older scp)

For compatibility with OpenSSH ≥ 9
This commit is contained in:
Michael Stapelberg 2022-04-17 15:32:45 +02:00
parent 7dbbe9b4b3
commit 48c5124500
2 changed files with 62 additions and 31 deletions

62
scp.go
View File

@ -66,36 +66,9 @@ func scpSink(channel ssh.Channel, req *ssh.Request, cmdline []string) error {
// Retrieve file contents
var cw countingWriter
tr := tar.NewReader(io.TeeReader(channel, &cw))
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
if err := unpackTar(io.TeeReader(channel, &cw)); err != nil {
return err
}
log.Printf("extracting %q", h.Name)
if err := os.MkdirAll(filepath.Dir(h.Name), 0700); err != nil {
return err
}
if strings.HasSuffix(h.Name, "/") {
continue // directory, dont try to OpenFile() it
}
mode := h.FileInfo().Mode() & os.ModePerm
out, err := renameio.NewPendingFile(h.Name, renameio.WithStaticPermissions(mode))
if err != nil {
return err
}
if _, err := io.Copy(out, tr); err != nil {
return err
}
if err := out.CloseAtomicallyReplace(); err != nil {
return err
}
}
if rest := size - int64(cw); rest > 0 {
buf := make([]byte, rest)
if _, err := channel.Read(buf); err != nil {
@ -125,3 +98,36 @@ func scpSink(channel ssh.Channel, req *ssh.Request, cmdline []string) error {
req.Reply(true, nil)
return nil
}
func unpackTar(r io.Reader) error {
tr := tar.NewReader(r)
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
log.Printf("extracting %q", h.Name)
if err := os.MkdirAll(filepath.Dir(h.Name), 0700); err != nil {
return err
}
if strings.HasSuffix(h.Name, "/") {
continue // directory, dont try to OpenFile() it
}
mode := h.FileInfo().Mode() & os.ModePerm
out, err := renameio.NewPendingFile(h.Name, renameio.WithStaticPermissions(mode))
if err != nil {
return err
}
if _, err := io.Copy(out, tr); err != nil {
return err
}
if err := out.CloseAtomicallyReplace(); err != nil {
return err
}
}
return nil
}

29
ssh.go
View File

@ -292,9 +292,34 @@ func (s *session) request(ctx context.Context, req *ssh.Request) error {
}
}
// See https://tools.ietf.org/html/rfc4254#section-6.10
_, err = s.channel.SendRequest("exit-status", false /* wantReply */, ssh.Marshal(exitStatus{exitCode}))
// Special case for breakglass usage: unpack all .tar files that were
// transferred into $PWD (which is a /tmp/breakglass… temporary
// directory), so that the binaries included in the tar file can be used
// for debugging.
dirents, err := os.ReadDir(".")
if err != nil {
return err
}
for _, dirent := range dirents {
if !strings.HasSuffix(dirent.Name(), ".tar") {
continue
}
f, err := os.Open(dirent.Name())
if err != nil {
return err
}
defer f.Close()
if err := unpackTar(f); err != nil {
return err
}
}
// See https://tools.ietf.org/html/rfc4254#section-6.10
if _, err := s.channel.SendRequest("exit-status", false /* wantReply */, ssh.Marshal(exitStatus{exitCode})); err != nil {
return err
}
return nil
case "shell":
req.Payload = []byte("\x00\x00\x00\x02sh")