~gitpocket/gitpocket

8
e3842ceec81f72b2c93afa6d77c9114a1b721e1b
mainfeed-discovery
gitpocket/forge/shell.go -rw-r--r--4.2 KiB ViewLogBlameView rawPermalink 9c68a68a5b feat(gists): host multi-file gists as git repositories with a cookie-less raw origin 8 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package forge

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")
	if id, ok := strings.CutPrefix(arg, "gist/"); ok {
		return gistShell(root, name, verb, id)
	}
	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" && !hooksReady(root) {
		return fail("pushes are unavailable right now")
	}
	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(append(argv, receiveArgs(root)...), pushLimit(root, repo.Owner.Name, canWrite)...)
		env = append(env, receiveEnv(repo, user)...)
	}
	err = syscall.Exec(git, append(argv, strings.TrimPrefix(verb, "git-"), dir), env)
	return fail(err.Error())
}

func gistShell(root, name, verb, id string) int {
	fail := func(msg string) int {
		fmt.Fprintln(os.Stderr, msg)
		return 1
	}
	if !gistID.MatchString(id) || verb == "git-upload-archive" {
		return fail("gist path must be gist/<id>")
	}
	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")
	}
	gist, err := store.Gist(id)
	if err != nil {
		return fail("gist not found")
	}
	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" {
		if gist.Owner.ID != user.ID {
			return fail("only the owner can push to this gist")
		}
		if !hooksReady(root) {
			return fail("pushes are unavailable right now")
		}
		if over, used, quota := overQuota(root, gist.Owner.Name); over {
			return fail(quotaMessage(used, quota))
		}
		argv = append(argv, gistReceiveArgs(root, gist.Owner.Name)...)
		env = append(env, gistEnv(gist, user)...)
	}
	err = syscall.Exec(git, append(argv, strings.TrimPrefix(verb, "git-"), gistDir(root, gist.Owner.Name, gist.ID)), env)
	return fail(err.Error())
}