💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › 2d02fe7c1b740dff30b5a7bbc8… captured on 2024-02-05 at 09:49:12. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-12-28)
-=-=-=-=-=-=-
0 package sshgit
1
2 import (
3 "github.com/gliderlabs/ssh"
4 "gemigit/access"
5 "gemigit/config"
6 "gemigit/db"
7 "log"
8 "strconv"
9 "strings"
10 "os/exec"
11 "os"
12 gossh "golang.org/x/crypto/ssh"
13 )
14
15 func handle(s ssh.Session) {
16
17 invalidPath := []byte("Invalid path\n")
18 notFound := []byte("Repository not found\n")
19 forbidden := []byte("Access forbidden\n")
20 readCommand := s.Command()[0] == "git-upload-pack"
21 writeCommand := s.Command()[0] == "git-receive-pack"
22
23 if (!readCommand && !writeCommand) {
24 s.Stderr().Write([]byte("invalid command\n"))
25 return
26 }
27 if len(s.Command()) < 2 {
28 s.Stderr().Write(invalidPath)
29 return
30 }
31 arg := s.Command()[1]
32 if arg[0] != '/' {
33 s.Stderr().Write(invalidPath)
34 return
35 }
36 arg = arg[1:]
37 if arg[len(arg) - 1] == '/' {
38 arg = arg[:len(arg) - 1]
39 }
40 args := strings.Split(arg, "/")
41 if len(args) != 2 {
42 s.Stderr().Write(invalidPath)
43 return
44 }
45 owner := args[0]
46 repo := args[1]
47 username := s.User()
48 password := s.Context().Value("password").(string)
49 readOnly := readCommand
50
51 public := false
52 if config.Cfg.Git.Public {
53 var err error
54 public, err = db.IsRepoPublic(repo, owner)
55 if err != nil {
56 s.Stderr().Write(notFound)
57 log.Println(err.Error())
58 return
59 }
60 }
61
62 if !public || !readOnly {
63 pass, err := db.CanUsePassword(repo, owner, username)
64 if err != nil {
65 log.Println(err.Error())
66 s.Stderr().Write(forbidden)
67 return
68 }
69 err = access.Login(username, password, true, pass, !readOnly)
70 if err != nil {
71 log.Println(err.Error())
72 s.Stderr().Write([]byte(err.Error() + "\n"))
73 return
74 }
75 if readOnly {
76 err = access.HasReadAccess(repo, owner, username)
77 } else {
78 err = access.HasWriteAccess(repo, owner, username)
79 }
80 if err != nil {
81 log.Println(err.Error())
82 s.Stderr().Write(forbidden)
83 return
84 }
85 }
86
87 var command string
88 if writeCommand {
89 command = "git-receive-pack"
90 } else {
91 command = "git-upload-pack"
92 }
93 cmd := exec.Command(command,
94 config.Cfg.Git.Path + "/" + owner + "/" + repo)
95 cmd.Stdin = s
96 cmd.Stdout = s
97 if err := cmd.Run(); err != nil {
98 log.Println(err)
99 return
100 }
101 }
102
103 func Listen(path string, address string, port int) {
104 var server ssh.Server
105 server.Handle(handle)
106 server.KeyboardInteractiveHandler = func(ctx ssh.Context,
107 challenge gossh.KeyboardInteractiveChallenge) bool {
108 if ctx.User() == "anon" {
109 ctx.SetValue("password", "")
110 return true
111 }
112 answers, err := challenge("", "",
113 []string{"password:"}, []bool{false})
114 if err != nil {
115 log.Println(err)
116 return false
117 }
118 ctx.SetValue("password", answers[0])
119 return true
120 }
121
122 server.Addr = config.Cfg.Git.SSH.Address + ":" +
123 strconv.Itoa(config.Cfg.Git.SSH.Port)
124 server.PasswordHandler = ssh.PasswordHandler(
125 func(ctx ssh.Context, password string) bool {
126 ctx.SetValue("password", password)
127 return true
128 })
129 data, err := os.ReadFile(config.Cfg.Gemini.Key)
130 if err != nil {
131 log.Fatalln(err)
132 return
133 }
134 key, err := gossh.ParsePrivateKey(data)
135 if err != nil {
136 log.Fatalln(err)
137 return
138 }
139 server.AddHostKey(key)
140 log.Println("SSH server started on port", config.Cfg.Git.SSH.Port)
141 if err := server.ListenAndServe(); err != nil {
142 log.Fatalln(err)
143 }
144 }
145