Goit

Simple and lightweight Git web server
git clone https://git.omkov.net/Goit
git clone [email protected]:Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2025-01-04 00:20:23
Commit77702b3600a7874e489fd943eeb51a85ea9fc0cb
Parent5d42ac7588aaad46c0909f8c6585f752cefede52

Implement goit-shell for Git over SSH

Diffstat

M Makefile | 1 +
A src/shell/main.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 112 insertions, 0 deletions

diff --git a/Makefile b/Makefile
index 9ccdde7..a205aa4 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,7 @@ VERSION ?= "dev"
 
 build: ## Build the project
 	@go build -ldflags "-X $(MODULE)/res.Version=$(VERSION)" -o ./bin/$(PROGRAM) ./src
+	@go build -ldflags "-X $(MODULE)/res.Version=$(VERSION)" -o ./bin/$(PROGRAM)-shell ./src/shell
 
 image: ## Build the project image
 	@docker build -t $(PROGRAM):$(VERSION) --build-arg version=$(VERSION) .
diff --git a/src/shell/main.go b/src/shell/main.go
new file mode 100644
index 0000000..c7a5b27
--- /dev/null
+++ b/src/shell/main.go
@@ -0,0 +1,111 @@
+// Copyright (C) 2025, Jakob Wakeling
+// All rights reserved.
+
+package main
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	"github.com/Jamozed/Goit/src/goit"
+	"github.com/Jamozed/Goit/src/util"
+	_ "github.com/mattn/go-sqlite3"
+)
+
+func main() {
+	if len(os.Args) < 2 || os.Getenv("SSH_ORIGINAL_COMMAND") == "" {
+		fmt.Fprint(os.Stderr, "Interactive Goit shell is not enabled.\n")
+		os.Exit(-1)
+	}
+
+	username := os.Args[1]
+	soc := os.Getenv("SSH_ORIGINAL_COMMAND")
+
+	/* Parse the SSH_ORIGINAL_COMMAND value for the direction and repository name. */
+	re := regexp.MustCompile(`^(git-upload-pack|git-receive-pack|git-upload-archive) '?/?(.*?)'?$`)
+	matches := re.FindStringSubmatch(soc)
+	if len(matches) != 3 || matches[1] == "" {
+		fmt.Fprintln(os.Stderr, "Unknown command:", soc)
+		os.Exit(-1)
+	}
+
+	command := matches[1]
+	action := util.If(strings.Contains(command, "upload"), "r", "w")
+	reponame := strings.TrimSuffix(strings.TrimSuffix(matches[2], "/"), ".git")
+
+	if !goit.IsLegal(reponame) {
+		fmt.Fprintln(os.Stderr, "Repository not found.")
+		os.Exit(-1)
+	}
+
+	/* Load the config. */
+	conf, err := goit.LoadConfig()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Failed to load config:", err.Error())
+		os.Exit(-1)
+	}
+
+	/* Open the database. */
+	db, err := sql.Open("sqlite3", filepath.Join(conf.DataPath, "goit.db?_timeout=5000"))
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Failed to open database:", err.Error())
+		os.Exit(-1)
+	}
+	defer db.Close()
+
+	/* Set global config and database variables correctly. */
+	goit.Conf = conf
+	goit.ShellSetDB(db)
+
+	/* Confirm that the database is the expected version. */
+	var version int
+	if err := db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
+		fmt.Fprintln(os.Stderr, "Failed to get database version:", err.Error())
+		os.Exit(-1)
+	}
+	if version != goit.LATEST_VERSION {
+		fmt.Fprintln(os.Stderr, "Unexpected database version.")
+		os.Exit(-1)
+	}
+
+	/* Confirm that the user exists and is enabled. */
+	user, err := goit.GetUserByName(username)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Failed to get user:", err.Error())
+		os.Exit(-1)
+	}
+	if user == nil {
+		fmt.Fprintln(os.Stderr, "User not found.")
+		os.Exit(-1)
+	}
+
+	/* Confirm that the repository exists. */
+	repo, err := goit.GetRepoByName(reponame)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Failed to get repository:", err.Error())
+		os.Exit(-1)
+	}
+
+	/* Check user authorization against repository. */
+	if repo == nil || (repo.Visibility == goit.Private && user.Id != repo.OwnerId) {
+		fmt.Fprintln(os.Stderr, "Repository not found.")
+		os.Exit(-1)
+	}
+
+	/* Check if user has write permissions for the repository, if required. */
+	if action == "w" && repo.OwnerId != user.Id {
+		fmt.Fprintln(os.Stderr, "Write access denied.")
+		os.Exit(-1)
+	}
+
+	/* Run git-shell command. */
+	if _, _, err := goit.NewGitCommand(
+		"shell", "-c", command+" '"+goit.RepoPath(reponame, true)+"'",
+	).Run(os.Stdin, os.Stdout); err != nil {
+		os.Exit(-1)
+	}
+}