diff options
author | Runxi Yu <me@runxiyu.org> | 2025-04-06 01:55:21 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-04-06 02:08:58 +0800 |
commit | faa5ca8fab23176d390e9522f1485d467851545b (patch) | |
tree | d3b1d081e0ea5e7f71a94dc1d301e2540a8abcc8 /internal/unsorted/ssh_server.go | |
parent | Slight refactor on NewServer (diff) | |
download | forge-faa5ca8fab23176d390e9522f1485d467851545b.tar.gz forge-faa5ca8fab23176d390e9522f1485d467851545b.tar.zst forge-faa5ca8fab23176d390e9522f1485d467851545b.zip |
Move stuff into internal/unsortedv0.1.28
Diffstat (limited to 'internal/unsorted/ssh_server.go')
-rw-r--r-- | internal/unsorted/ssh_server.go | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/internal/unsorted/ssh_server.go b/internal/unsorted/ssh_server.go new file mode 100644 index 0000000..eb4d09d --- /dev/null +++ b/internal/unsorted/ssh_server.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> + +package unsorted + +import ( + "fmt" + "log/slog" + "net" + "os" + "strings" + + gliderSSH "github.com/gliderlabs/ssh" + "go.lindenii.runxiyu.org/forge/internal/ansiec" + "go.lindenii.runxiyu.org/forge/internal/misc" + goSSH "golang.org/x/crypto/ssh" +) + +// serveSSH serves SSH on a [net.Listener]. The listener should generally be a +// TCP listener, although AF_UNIX SOCK_STREAM listeners may be appropriate in +// rare cases. +func (s *Server) serveSSH(listener net.Listener) error { + var hostKeyBytes []byte + var hostKey goSSH.Signer + var err error + var server *gliderSSH.Server + + if hostKeyBytes, err = os.ReadFile(s.config.SSH.Key); err != nil { + return err + } + + if hostKey, err = goSSH.ParsePrivateKey(hostKeyBytes); err != nil { + return err + } + + s.serverPubkey = hostKey.PublicKey() + s.serverPubkeyString = misc.BytesToString(goSSH.MarshalAuthorizedKey(s.serverPubkey)) + s.serverPubkeyFP = goSSH.FingerprintSHA256(s.serverPubkey) + + server = &gliderSSH.Server{ + Handler: func(session gliderSSH.Session) { + clientPubkey := session.PublicKey() + var clientPubkeyStr string + if clientPubkey != nil { + clientPubkeyStr = strings.TrimSuffix(misc.BytesToString(goSSH.MarshalAuthorizedKey(clientPubkey)), "\n") + } + + slog.Info("incoming ssh", "addr", session.RemoteAddr().String(), "key", clientPubkeyStr, "command", session.RawCommand()) + fmt.Fprintln(session.Stderr(), ansiec.Blue+"Lindenii Forge "+version+", source at "+strings.TrimSuffix(s.config.HTTP.Root, "/")+"/-/source/"+ansiec.Reset+"\r") + + cmd := session.Command() + + if len(cmd) < 2 { + fmt.Fprintln(session.Stderr(), "Insufficient arguments\r") + return + } + + switch cmd[0] { + case "git-upload-pack": + if len(cmd) > 2 { + fmt.Fprintln(session.Stderr(), "Too many arguments\r") + return + } + err = s.sshHandleUploadPack(session, clientPubkeyStr, cmd[1]) + case "git-receive-pack": + if len(cmd) > 2 { + fmt.Fprintln(session.Stderr(), "Too many arguments\r") + return + } + err = s.sshHandleRecvPack(session, clientPubkeyStr, cmd[1]) + default: + fmt.Fprintln(session.Stderr(), "Unsupported command: "+cmd[0]+"\r") + return + } + if err != nil { + fmt.Fprintln(session.Stderr(), err.Error()) + return + } + }, + PublicKeyHandler: func(_ gliderSSH.Context, _ gliderSSH.PublicKey) bool { return true }, + KeyboardInteractiveHandler: func(_ gliderSSH.Context, _ goSSH.KeyboardInteractiveChallenge) bool { return true }, + // It is intentional that we do not check any credentials and accept all connections. + // This allows all users to connect and clone repositories. However, the public key + // is passed to handlers, so e.g. the push handler could check the key and reject the + // push if it needs to. + } //exhaustruct:ignore + + server.AddHostKey(hostKey) + + if err = server.Serve(listener); err != nil { + slog.Error("error serving SSH", "error", err.Error()) + os.Exit(1) + } + + return nil +} |