Goit

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

Goit/src/shell/main.go (112 lines, 2.9 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
// 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)
	}
}