// testsshd is a minimal SSH server for rsend integration tests: one host key,
// an authorized_keys allowlist, and command execution via /bin/sh (so it runs
// the remote rsync). It is not a product component; it stands in for a real
// sshd so the full push path can be exercised on a device or in CI.
package main

import (
	"flag"
	"io"
	"log"
	"net"
	"os"
	"os/exec"

	"golang.org/x/crypto/ssh"
)

func main() {
	addr := flag.String("addr", "127.0.0.1:2222", "listen address")
	hostKeyPath := flag.String("hostkey", "", "OpenSSH private host key (required)")
	authPath := flag.String("authorized", "", "authorized_keys file (required)")
	flag.Parse()

	if *hostKeyPath == "" || *authPath == "" {
		log.Fatal("testsshd: -hostkey and -authorized are required")
	}

	hostKey := mustSigner(*hostKeyPath)
	allowed := mustAuthorized(*authPath)

	cfg := &ssh.ServerConfig{
		PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			if allowed[string(key.Marshal())] {
				return &ssh.Permissions{}, nil
			}
			return nil, io.EOF
		},
	}
	cfg.AddHostKey(hostKey)

	ln, err := net.Listen("tcp", *addr)
	if err != nil {
		log.Fatalf("testsshd: listen: %v", err)
	}
	log.Printf("testsshd: listening on %s", ln.Addr())
	for {
		c, err := ln.Accept()
		if err != nil {
			log.Fatalf("testsshd: accept: %v", err)
		}
		go handle(c, cfg)
	}
}

func mustSigner(path string) ssh.Signer {
	b, err := os.ReadFile(path)
	if err != nil {
		log.Fatalf("testsshd: read host key: %v", err)
	}
	s, err := ssh.ParsePrivateKey(b)
	if err != nil {
		log.Fatalf("testsshd: parse host key: %v", err)
	}
	return s
}

func mustAuthorized(path string) map[string]bool {
	b, err := os.ReadFile(path)
	if err != nil {
		log.Fatalf("testsshd: read authorized_keys: %v", err)
	}
	m := map[string]bool{}
	for len(b) > 0 {
		key, _, _, rest, err := ssh.ParseAuthorizedKey(b)
		if err != nil {
			break
		}
		m[string(key.Marshal())] = true
		b = rest
	}
	if len(m) == 0 {
		log.Fatal("testsshd: no authorized keys")
	}
	return m
}

func handle(nConn net.Conn, cfg *ssh.ServerConfig) {
	conn, chans, reqs, err := ssh.NewServerConn(nConn, cfg)
	if err != nil {
		return
	}
	defer conn.Close()
	go ssh.DiscardRequests(reqs)
	for nc := range chans {
		if nc.ChannelType() != "session" {
			nc.Reject(ssh.UnknownChannelType, "only session")
			continue
		}
		ch, requests, err := nc.Accept()
		if err != nil {
			return
		}
		go session(ch, requests)
	}
}

func session(ch ssh.Channel, requests <-chan *ssh.Request) {
	for req := range requests {
		if req.Type != "exec" {
			if req.WantReply {
				req.Reply(false, nil)
			}
			continue
		}
		var payload struct{ Command string }
		ssh.Unmarshal(req.Payload, &payload)
		if req.WantReply {
			req.Reply(true, nil)
		}
		code := run(payload.Command, ch)
		ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{uint32(code)}))
		ch.Close()
		return
	}
}

func run(cmd string, ch ssh.Channel) int {
	c := exec.Command("/bin/sh", "-c", cmd)
	c.Stdout = ch
	c.Stderr = ch.Stderr()
	in, err := c.StdinPipe()
	if err != nil {
		return 1
	}
	if err := c.Start(); err != nil {
		return 1
	}
	go func() {
		io.Copy(in, ch)
		in.Close()
	}()
	if err := c.Wait(); err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			return ee.ExitCode()
		}
		return 1
	}
	return 0
}
