Skip to content

Commit

Permalink
client: fix module listing via rsync daemon protocol
Browse files Browse the repository at this point in the history
It worked with SSH, but not via daemon protocol.
The newly added test covers this.
  • Loading branch information
stapelberg committed Feb 24, 2025
1 parent 211e10d commit 8132c53
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 19 deletions.
26 changes: 25 additions & 1 deletion integration/interop/interop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestTridgeRsyncVersion(t *testing.T) {
}
}

func TestModuleListing(t *testing.T) {
func TestModuleListingServer(t *testing.T) {
t.Parallel()

rsyncBin := rsynctest.TridgeOrGTFO(t, "TODO: add reason")
Expand Down Expand Up @@ -90,6 +90,30 @@ func TestModuleListing(t *testing.T) {
}
}

func TestModuleListingClient(t *testing.T) {
t.Parallel()

tmp := t.TempDir()

// start a server to sync from
srv := rsynctest.New(t, rsynctest.InteropModule(tmp))

// request module list
args := []string{
"gokr-rsync",
"-aH",
"rsync://localhost:" + srv.Port + "/",
}
var stdout bytes.Buffer
if _, err := maincmd.Main(t.Context(), args, os.Stdin, &stdout, &stdout, nil); err != nil {
t.Fatal(err)
}

if want := "interop\tinterop"; !strings.Contains(stdout.String(), want) {
t.Fatalf("rsync output unexpectedly did not contain %q:\n%s", want, stdout.String())
}
}

func TestInterop(t *testing.T) {
t.Parallel()

Expand Down
6 changes: 5 additions & 1 deletion internal/maincmd/clientmaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ func rsyncMain(osenv receiver.Osenv, opts *rsyncopts.Options, sources []string,
}
negotiate := true
if daemonConnection != 0 {
if err := startInbandExchange(opts, conn, module, path); err != nil {
done, err := startInbandExchange(osenv, opts, conn, module, path)
if err != nil {
return nil, err
}
if done {
return nil, nil
}
negotiate = false // already done
}
stats, err := clientRun(osenv, opts, conn, other, negotiate)
Expand Down
37 changes: 20 additions & 17 deletions internal/maincmd/clientserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -35,17 +34,18 @@ func socketClient(osenv receiver.Osenv, opts *rsyncopts.Options, host string, pa
if err != nil {
return nil, err
}
if path == "" {
return nil, fmt.Errorf("empty remote path")
}
module := path
if idx := strings.IndexByte(module, '/'); idx > -1 {
module = module[:idx]
}
log.Printf("rsync module %q, path %q", module, path)
if err := startInbandExchange(opts, conn, module, path); err != nil {
done, err := startInbandExchange(osenv, opts, conn, module, path)
if err != nil {
return nil, err
}
if done {
return nil, nil
}
stats, err := clientRun(osenv, opts, conn, dest, false)
if err != nil {
return nil, err
Expand All @@ -54,7 +54,7 @@ func socketClient(osenv receiver.Osenv, opts *rsyncopts.Options, host string, pa
}

// rsync/clientserver.c:start_inband_exchange
func startInbandExchange(opts *rsyncopts.Options, conn io.ReadWriter, module, path string) error {
func startInbandExchange(osenv receiver.Osenv, opts *rsyncopts.Options, conn io.ReadWriter, module, path string) (done bool, _ error) {
rd := bufio.NewReader(conn)

// send client greeting
Expand All @@ -63,23 +63,23 @@ func startInbandExchange(opts *rsyncopts.Options, conn io.ReadWriter, module, pa
// read server greeting
serverGreeting, err := rd.ReadString('\n')
if err != nil {
return fmt.Errorf("ReadString: %v", err)
return false, fmt.Errorf("ReadString: %v", err)
}
serverGreeting = strings.TrimSpace(serverGreeting)
const serverGreetingPrefix = "@RSYNCD: "
if !strings.HasPrefix(serverGreeting, serverGreetingPrefix) {
return fmt.Errorf("invalid server greeting: got %q", serverGreeting)
return false, fmt.Errorf("invalid server greeting: got %q", serverGreeting)
}
// protocol negotiation: require at least version 27
serverGreeting = strings.TrimPrefix(serverGreeting, serverGreetingPrefix)
var remoteProtocol, remoteSub int32
if _, err := fmt.Sscanf(serverGreeting, "%d.%d", &remoteProtocol, &remoteSub); err != nil {
if _, err := fmt.Sscanf(serverGreeting, "%d", &remoteProtocol); err != nil {
return fmt.Errorf("reading server greeting: %v", err)
return false, fmt.Errorf("reading server greeting: %v", err)
}
}
if remoteProtocol < 27 {
return fmt.Errorf("server version %d too old", remoteProtocol)
return false, fmt.Errorf("server version %d too old", remoteProtocol)
}

log.Printf("(Client) Protocol versions: remote=%d, negotiated=%d", remoteProtocol, rsync.ProtocolVersion)
Expand All @@ -90,28 +90,31 @@ func startInbandExchange(opts *rsyncopts.Options, conn io.ReadWriter, module, pa
for {
line, err := rd.ReadString('\n')
if err != nil {
return fmt.Errorf("did not get server startup line: %v", err)
return false, fmt.Errorf("did not get server startup line: %v", err)
}
line = strings.TrimSpace(line)
log.Printf("read line: %q", line)

if strings.HasPrefix(line, "@RSYNCD: AUTHREQD ") {
// TODO: implement support for authentication
return fmt.Errorf("authentication not yet implemented")
return false, fmt.Errorf("authentication not yet implemented")
}

if line == "@RSYNCD: OK" {
break
}

// TODO: @RSYNCD: EXIT after listing modules
if line == "@RSYNCD: EXIT" {
return true, nil
}

if strings.HasPrefix(line, "@ERROR") {
fmt.Fprintf(os.Stderr, "%s\n", line)
return fmt.Errorf("abort (rsync fatal error)")
fmt.Fprintf(osenv.Stderr, "%s\n", line)
return false, fmt.Errorf("abort (rsync fatal error)")
}

// print rsync server message of the day (MOTD)
fmt.Fprintf(os.Stdout, "%s\n", line)
fmt.Fprintf(osenv.Stdout, "%s\n", line)
}

sargv := serverOptions(opts)
Expand All @@ -125,5 +128,5 @@ func startInbandExchange(opts *rsyncopts.Options, conn io.ReadWriter, module, pa
}
fmt.Fprintf(conn, "\n")

return nil
return false, nil
}

0 comments on commit 8132c53

Please sign in to comment.