fix subsystem invocation: send exit code afterwards

This fixes scp(1) with OpenSSH ≥ 9.
This commit is contained in:
Michael Stapelberg 2022-04-17 15:23:09 +02:00
parent 097a6f87d6
commit 7dbbe9b4b3

36
ssh.go
View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -210,6 +209,11 @@ type subsystem struct {
SubsystemName string SubsystemName string
} }
// exitStatus is a message for returning exit status as specified in RFC4254, Section 6.10
type exitStatus struct {
Status uint32
}
func findShell() string { func findShell() string {
if path, err := exec.LookPath("sh"); err == nil { if path, err := exec.LookPath("sh"); err == nil {
return path return path
@ -270,23 +274,27 @@ func (s *session) request(ctx context.Context, req *ssh.Request) error {
log.Printf("starting SFTP subsystem") log.Printf("starting SFTP subsystem")
req.Reply(true, nil)
srv, err := sftp.NewServer(s.channel, sftp.WithDebug(os.Stderr)) srv, err := sftp.NewServer(s.channel, sftp.WithDebug(os.Stderr))
if err != nil { if err != nil {
return err return err
} }
go func() { exitCode := uint32(0)
err := srv.Serve() if err := srv.Serve(); err != nil {
if err != nil { log.Printf("(sftp.Server).Serve(): %v", err)
log.Printf("(sftp.Server).Serve(): %v", err) if err == io.EOF {
if err == io.EOF { defer srv.Close()
srv.Close() log.Printf("sftp client exited session")
log.Printf("sftp client exited session") } else {
} exitCode = 1
} }
}() }
req.Reply(true, nil) // See https://tools.ietf.org/html/rfc4254#section-6.10
_, err = s.channel.SendRequest("exit-status", false /* wantReply */, ssh.Marshal(exitStatus{exitCode}))
return err
case "shell": case "shell":
req.Payload = []byte("\x00\x00\x00\x02sh") req.Payload = []byte("\x00\x00\x00\x02sh")
@ -353,13 +361,13 @@ func (s *session) request(ctx context.Context, req *ssh.Request) error {
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Printf("err: %v", err) log.Printf("err: %v", err)
} }
status := make([]byte, 4) var status exitStatus
if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok { if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
binary.BigEndian.PutUint32(status, uint32(ws.ExitStatus())) status.Status = uint32(ws.ExitStatus())
} }
// See https://tools.ietf.org/html/rfc4254#section-6.10 // See https://tools.ietf.org/html/rfc4254#section-6.10
if _, err := s.channel.SendRequest("exit-status", false /* wantReply */, status); err != nil { if _, err := s.channel.SendRequest("exit-status", false /* wantReply */, ssh.Marshal(status)); err != nil {
log.Printf("err2: %v", err) log.Printf("err2: %v", err)
} }
s.channel.Close() s.channel.Close()