~gitpocket/gitpocket

1
3b1371e84b8b46e3c441a3dae54a34d06d1012a3
mainfeed-discovery
gitpocket/shell.go -rw-r--r--2.6 KiB ViewLogBlameView rawPermalink 9055e1efa3 fix(security): escape readme link bases, validate branches and emails, strip header controls, add csp and throttling 3 hours ago
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
)

func keys(root, binary string, args []string) int {
	if len(args) != 4 {
		return 1
	}
	store, err := openStore(filepath.Join(root, "gitpocket.db"), true)
	if err != nil {
		return 1
	}
	user, err := store.UserByKey(args[2] + " " + args[3])
	if err != nil {
		return 1
	}
	fmt.Printf("command=\"%s -root %s shell %s\",restrict %s %s\n", binary, root, user.Name, args[2], args[3])
	return 0
}

func shell(root, name string) int {
	original := os.Getenv("SSH_ORIGINAL_COMMAND")
	fail := func(msg string) int {
		fmt.Fprintln(os.Stderr, msg)
		return 1
	}
	if original == "" {
		return fail("hello " + name + ", this host only serves git")
	}
	verb, arg, ok := strings.Cut(original, " ")
	if !ok {
		return fail("unsupported command")
	}
	switch verb {
	case "git-upload-pack", "git-receive-pack", "git-upload-archive":
	default:
		return fail("unsupported command")
	}
	arg = strings.Trim(strings.TrimSpace(arg), "'\"")
	arg = strings.TrimPrefix(strings.TrimPrefix(arg, "/"), "~")
	arg = strings.TrimSuffix(arg, ".git")
	owner, repoName, ok := strings.Cut(arg, "/")
	if !ok || !validName(owner) || !validName(repoName) {
		return fail("repository path must be ~owner/repo")
	}
	store, err := openStore(filepath.Join(root, "gitpocket.db"), true)
	if err != nil {
		return fail("database unavailable")
	}
	user, err := store.UserByName(name)
	if err != nil {
		return fail("unknown user")
	}
	repo, err := store.RepoByPath(owner, repoName)
	if err != nil {
		moved, ok := store.Redirect(owner, repoName)
		if !ok {
			return fail("repository not found")
		}
		repo = moved
	}
	canWrite := store.CanWrite(repo, &user)
	if repo.Private && !canWrite {
		return fail("repository not found")
	}
	if verb == "git-receive-pack" && !canWrite {
		return fail("you do not have write access to " + repo.Path())
	}
	if verb == "git-receive-pack" && repo.Archived {
		return fail("repository is archived and read only")
	}
	if verb == "git-receive-pack" {
		if over, used, quota := overQuota(root, repo.Owner.Name); over {
			return fail(quotaMessage(used, quota))
		}
	}
	dir := filepath.Join(root, "repos", repo.Owner.Name, repo.Name+".git")
	git, err := exec.LookPath("git")
	if err != nil {
		return fail("git not installed")
	}
	env := []string{"PATH=" + os.Getenv("PATH"), "HOME=" + os.Getenv("HOME")}
	if p := os.Getenv("GIT_PROTOCOL"); p != "" {
		env = append(env, "GIT_PROTOCOL="+p)
	}
	argv := []string{"git"}
	if verb == "git-receive-pack" {
		argv = append(argv, pushLimit(root, repo.Owner.Name)...)
	}
	err = syscall.Exec(git, append(argv, strings.TrimPrefix(verb, "git-"), dir), env)
	return fail(err.Error())
}