From 78ef3b19d40aa6f63412ba961ab5c8bf0939237e Mon Sep 17 00:00:00 2001
From: Runxi Yu <me@runxiyu.org>
Date: Wed, 12 Feb 2025 17:08:27 +0800
Subject: [PATCH] ssh.go: Add anonymous SSH cloning

---
 config.go  |  5 +++++
 forge.scfg |  6 ++++++
 go.mod     |  4 +++-
 main.go    |  5 +++++
 ssh.go     | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++

diff --git a/config.go b/config.go
index 2445d88b4356260a04becc5d81171de7eece8e3b..bf4e571ac5ad23d38fe6735eae8f228674ee0d3c 100644
--- a/config.go
+++ b/config.go
@@ -19,6 +19,11 @@ 	HTTP struct {
 		Net  string `scfg:"net"`
 		Addr string `scfg:"addr"`
 	} `scfg:"http"`
+	SSH struct {
+		Net  string `scfg:"net"`
+		Addr string `scfg:"addr"`
+		Key  string `scfg:"key"`
+	} `scfg:"ssh"`
 	Git struct {
 		Root string `scfg:"root"`
 	} `scfg:"git"`
diff --git a/forge.scfg b/forge.scfg
index b414410180c4a07510b60686613a8d28f282681e..194c0460b7f11e81372cfcdc995bd7d41b01c97b 100644
--- a/forge.scfg
+++ b/forge.scfg
@@ -3,6 +3,12 @@ 	net tcp
 	addr :8080
 }
 
+ssh {
+	net ssh
+	addr :2222
+	key /etc/ssh/ssh_host_ed25519_key
+}
+
 db {
 	type postgres
 	conn postgresql:///lindenii-forge?host=/var/run/postgresql
diff --git a/go.mod b/go.mod
index 14fe3929fa2c40f2a8decd187e22068119567d69..2b46fbc6a3aad6e2e66658b5335497184b999a5f 100644
--- a/go.mod
+++ b/go.mod
@@ -4,18 +4,21 @@ go 1.23.5
 
 require (
 	github.com/alecthomas/chroma/v2 v2.15.0
+	github.com/gliderlabs/ssh v0.3.8
 	github.com/go-git/go-git/v5 v5.13.2
 	github.com/jackc/pgx/v5 v5.7.2
 	github.com/microcosm-cc/bluemonday v1.0.27
 	github.com/niklasfasching/go-org v1.7.0
 	github.com/yuin/goldmark v1.7.8
 	go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250211153243-8946fae17bd0
+	golang.org/x/crypto v0.33.0
 )
 
 require (
 	dario.cat/mergo v1.0.1 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/ProtonMail/go-crypto v1.1.5 // indirect
+	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/cloudflare/circl v1.6.0 // indirect
 	github.com/cyphar/filepath-securejoin v0.4.1 // indirect
@@ -34,7 +37,6 @@ 	github.com/pjbgf/sha1cd v0.3.2 // indirect
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
 	github.com/skeema/knownhosts v1.3.1 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
-	golang.org/x/crypto v0.33.0 // indirect
 	golang.org/x/net v0.35.0 // indirect
 	golang.org/x/sync v0.11.0 // indirect
 	golang.org/x/sys v0.30.0 // indirect
diff --git a/main.go b/main.go
index cb5943893d74102fb850eb67dafa338a8c60bfd7..0e0f91ed1a017e4db159ebc4d64ce20d10a87db4 100644
--- a/main.go
+++ b/main.go
@@ -31,6 +31,11 @@ 	if err != nil {
 		clog.Fatal(1, "Listening: "+err.Error())
 	}
 
+	err = serve_ssh()
+	if err != nil {
+		clog.Fatal(1, "Listening SSH: "+err.Error())
+	}
+
 	err = http.Serve(listener, &http_router_t{})
 	if err != nil {
 		clog.Fatal(1, "Serving: "+err.Error())
diff --git a/ssh.go b/ssh.go
new file mode 100644
index 0000000000000000000000000000000000000000..e1b9ff19a70ce4ace164cb1d1b4f7c62f45dcadf
--- /dev/null
+++ b/ssh.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+
+	glider_ssh "github.com/gliderlabs/ssh"
+	"go.lindenii.runxiyu.org/lindenii-common/clog"
+	go_ssh "golang.org/x/crypto/ssh"
+)
+
+func serve_ssh() error {
+	hostKeyBytes, err := os.ReadFile(config.SSH.Key)
+	if err != nil {
+		return err
+	}
+
+	hostKey, err := go_ssh.ParsePrivateKey(hostKeyBytes)
+	if err != nil {
+		return err
+	}
+
+	server := &glider_ssh.Server{
+		Handler: func(session glider_ssh.Session) {
+			client_public_key := session.PublicKey()
+			var client_public_key_string string
+			if client_public_key != nil {
+				client_public_key_string = string(go_ssh.MarshalAuthorizedKey(client_public_key))
+			}
+			_ = client_public_key_string
+
+			cmd := session.Command()
+
+			if len(cmd) < 2 {
+				fmt.Fprintln(session.Stderr(), "Insufficient arguments")
+				return
+			}
+
+			if cmd[0] != "git-upload-pack" {
+				fmt.Fprintln(session.Stderr(), "Unsupported command")
+				return
+			}
+
+			proc := exec.CommandContext(session.Context(), cmd[0], "/home/runxiyu/git/forge.git")
+			proc.Stdin = session
+			proc.Stdout = session
+			proc.Stderr = session.Stderr()
+
+			err := proc.Start()
+			if err != nil {
+				fmt.Fprintln(session.Stderr(), "Error while starting process:", err)
+				return
+			}
+			err = proc.Wait()
+			if exitError, ok := err.(*exec.ExitError); ok {
+				fmt.Fprintln(session.Stderr(), "Process exited with error", exitError.ExitCode())
+			} else if err != nil {
+				fmt.Fprintln(session.Stderr(), "Error while waiting for process:", err)
+			}
+		},
+		PublicKeyHandler:           func(ctx glider_ssh.Context, key glider_ssh.PublicKey) bool { return true },
+		KeyboardInteractiveHandler: func(ctx glider_ssh.Context, challenge go_ssh.KeyboardInteractiveChallenge) bool { return true },
+	}
+
+	server.AddHostKey(hostKey)
+
+	listener, err := net.Listen("tcp", ":2222")
+	if err != nil {
+		return err
+	}
+
+	go func() {
+		err = server.Serve(listener)
+		if err != nil {
+			clog.Fatal(1, "Serving SSH: "+err.Error())
+		}
+	}()
+
+	return nil
+}

-- 
2.48.1