diff options
| author | Max Resnick <max@ofmax.li> | 2022-10-29 08:41:15 -0700 |
|---|---|---|
| committer | Max Resnick <max@ofmax.li> | 2022-10-29 08:45:29 -0700 |
| commit | d0cb5e2318d1859f2dc9027151b4e4f1c973c6a1 (patch) | |
| tree | e6b0b6cf1a88a773f4f3b84e6949a73621d50a0f /main.go | |
| download | go-git-server-d0cb5e2318d1859f2dc9027151b4e4f1c973c6a1.tar.gz | |
initial commit
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 126 |
1 files changed, 126 insertions, 0 deletions
@@ -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)) +} |