aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2022-10-29 08:41:15 -0700
committerMax Resnick <max@ofmax.li>2022-10-29 08:45:29 -0700
commitd0cb5e2318d1859f2dc9027151b4e4f1c973c6a1 (patch)
treee6b0b6cf1a88a773f4f3b84e6949a73621d50a0f /main.go
downloadgo-git-server-d0cb5e2318d1859f2dc9027151b4e4f1c973c6a1.tar.gz
initial commit
Diffstat (limited to 'main.go')
-rw-r--r--main.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..0db6ee1
--- /dev/null
+++ b/main.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "encoding/csv"
+ "flag"
+ "fmt"
+ "log"
+ "math/big"
+ "net/http"
+ "net/http/cgi"
+ "os"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+var (
+ reposDir = flag.String("r", ".", "Directory containing git repositories")
+ backendCommand = flag.String("c", "git http-backend", "CGI binary to execute")
+ addr = flag.String("l", ":8080", "Address/port to listen on")
+ newToken = flag.Bool("t", false, "make a new token")
+ authMap = map[string]string{}
+)
+
+func LoadTokens() (map[string]string, error) {
+ tokenMap := make(map[string]string)
+ contents, err := os.Open("tokens.csv")
+ if err != nil {
+ fmt.Println("File reading error", err)
+ return tokenMap, err
+ }
+ defer contents.Close()
+ r := csv.NewReader(contents)
+ tokens, err := r.ReadAll()
+ if err != nil {
+ fmt.Println("File reading error", err)
+ return tokenMap, err
+ }
+ for _, acctToken := range tokens {
+ acct, hash := acctToken[0], acctToken[1]
+ tokenMap[acct] = hash
+ }
+ return tokenMap, err
+}
+
+func NewToken() (string, string, error) {
+ tokenBytes := make([]byte, 28)
+ for i := range tokenBytes {
+ max := big.NewInt(int64(255))
+ randInt, err := rand.Int(rand.Reader, max)
+ if err != nil {
+ return "", "", err
+ }
+ tokenBytes[i] = uint8(randInt.Int64())
+ }
+ hashBytes, err := bcrypt.GenerateFromPassword(tokenBytes, bcrypt.DefaultCost)
+ if err != nil {
+ return "", "", err
+ }
+ token := base64.URLEncoding.EncodeToString(tokenBytes)
+ hash := string(hashBytes)
+ return token, hash, nil
+}
+
+type Handler struct {
+ cgiHandler *cgi.Handler
+}
+
+func NewHandler(reposDir, backendCommand string) *Handler {
+ return &Handler{
+ &cgi.Handler{
+ Path: "/bin/sh",
+ Args: []string{"-c", backendCommand},
+ Dir: ".",
+ Env: []string{
+ fmt.Sprintf("GIT_PROJECT_ROOT=%v", reposDir),
+ "GIT_HTTP_EXPORT_ALL=1",
+ },
+ },
+ }
+}
+
+func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ u, p, ok := req.BasicAuth()
+ if !ok {
+ rw.Header().Set("WWW-Authenticate", `Basic realm="git"`)
+ http.Error(rw, "Authentication Required", 401)
+ return
+ }
+ hash, ok := authMap[fmt.Sprintf("user:%s", u)]
+ if !ok {
+ http.Error(rw, "Bad Request", 400)
+ return
+ }
+ token, err := base64.URLEncoding.DecodeString(p)
+ if err != nil {
+ http.Error(rw, "Bad Request", 400)
+ return
+ }
+ if err := bcrypt.CompareHashAndPassword([]byte(hash), token); err != nil {
+ http.Error(rw, "Bad Request", 400)
+ return
+ }
+ h.cgiHandler.ServeHTTP(rw, req)
+}
+
+func main() {
+ flag.Parse()
+ if *newToken {
+ token, hash, err := NewToken()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("token: %s\nhash: %s\n", token, hash)
+ return
+ }
+ tokens, err := LoadTokens()
+ fmt.Println(tokens)
+ if err != nil {
+ log.Fatal(err)
+ }
+ authMap = tokens
+ http.Handle("/", NewHandler(*reposDir, *backendCommand))
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}