forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

knotserver: reject 'git-receive-pack' service in info/refs

* Checks service name query parameter in info/refs handler; rejects any
service request that doesn't match 'git-upload-pack'.

* Produces 'text/plain' error responses so the git client will display
them.

* Adds a specific reject message for 'git-receive-pack'. This will
produce a less cryptic error in the terminal when trying to push over
http.

Changed files
+45 -8
knotserver
+45 -8
knotserver/git.go
···
import (
"compress/gzip"
+
"fmt"
"io"
"net/http"
"path/filepath"
···
func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) {
did := chi.URLParam(r, "did")
name := chi.URLParam(r, "name")
-
repo, _ := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
+
repoName, err := securejoin.SecureJoin(did, name)
+
if err != nil {
+
gitError(w, "repository not found", http.StatusNotFound)
+
d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
+
return
+
}
-
w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
-
w.WriteHeader(http.StatusOK)
+
repoPath, err := securejoin.SecureJoin(d.c.Repo.ScanPath, repoName)
+
if err != nil {
+
gitError(w, "repository not found", http.StatusNotFound)
+
d.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
+
return
+
}
cmd := service.ServiceCommand{
-
Dir: repo,
+
Dir: repoPath,
Stdout: w,
}
-
if err := cmd.InfoRefs(); err != nil {
-
writeError(w, err.Error(), 500)
-
d.l.Error("git: failed to execute git-upload-pack (info/refs)", "handler", "InfoRefs", "error", err)
-
return
+
serviceName := r.URL.Query().Get("service")
+
switch serviceName {
+
case "git-upload-pack":
+
w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
+
w.WriteHeader(http.StatusOK)
+
+
if err := cmd.InfoRefs(); err != nil {
+
gitError(w, err.Error(), http.StatusInternalServerError)
+
d.l.Error("git: process failed", "handler", "InfoRefs", "service", serviceName, "error", err)
+
return
+
}
+
case "git-receive-pack":
+
d.RejectPush(w, r, name)
+
default:
+
gitError(w, fmt.Sprintf("service unsupported: '%s'", serviceName), http.StatusForbidden)
}
}
···
return
}
}
+
+
func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
+
// A text/plain response will cause git to print each line of the body
+
// prefixed with "remote: ".
+
w.Header().Set("content-type", "text/plain; charset=UTF-8")
+
w.WriteHeader(http.StatusForbidden)
+
+
fmt.Fprintf(w, "Welcome to Tangled.sh!\n\nPushes are currently only supported over SSH.")
+
fmt.Fprintf(w, "\n\n")
+
}
+
+
func gitError(w http.ResponseWriter, msg string, status int) {
+
w.Header().Set("content-type", "text/plain; charset=UTF-8")
+
w.WriteHeader(status)
+
fmt.Fprintf(w, "%s\n", msg)
+
}