diff --git a/go.mod b/go.mod index 4cab77a..2f89157 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,9 @@ module git.send.nrw/sendnrw/go-pgp-server go 1.24.4 +require github.com/ProtonMail/go-crypto v1.3.0 + require ( - github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/sys v0.30.0 // indirect diff --git a/main.go b/main.go index cc080b8..7ba1028 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "sync" "time" @@ -210,6 +211,21 @@ func sanitizeFilename(name string) string { return name } +func getenv(k, d string) string { + if v := os.Getenv(k); v != "" { + return v + } + return d +} + +func enabled(k string, def bool) bool { + b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k))) + if err != nil { + return def + } + return b +} + func genID(email, fingerprint string) string { base := strings.ToLower(strings.TrimSpace(email)) fp := strings.ToUpper(strings.TrimSpace(fingerprint)) @@ -306,62 +322,68 @@ func main() { }) // Upload with automatic fingerprint parsing mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - if err := r.ParseMultipartForm(maxUploadSize); err != nil { - http.Error(w, "invalid form", http.StatusBadRequest) - return - } - name := strings.TrimSpace(r.FormValue("name")) - email := strings.TrimSpace(r.FormValue("email")) - userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override - file, hdr, err := r.FormFile("file") - if err != nil { - http.Error(w, "missing file", http.StatusBadRequest) - return - } - defer file.Close() - var buf bytes.Buffer - lr := io.LimitedReader{R: file, N: maxUploadSize} - if _, err := io.Copy(&buf, &lr); err != nil { - http.Error(w, "read error", http.StatusBadRequest) - return - } - b := buf.Bytes() - if !isASCII(string(b)) || !bytes.Contains(b, []byte("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { - http.Error(w, "file must be ASCII-armored PGP public key (.asc)", http.StatusBadRequest) - return - } - // Parse fingerprint - autoFPR, parseErr := parseFingerprintFromASCII(b) - fpr := userFPR - if fpr == "" && parseErr == nil { - fpr = autoFPR - } - if fpr == "" { - http.Error(w, "could not parse fingerprint; please provide it manually", http.StatusBadRequest) - return - } - fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", "")) + if enabled("WRITEACCESS", false) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + if err := r.ParseMultipartForm(maxUploadSize); err != nil { + http.Error(w, "invalid form", http.StatusBadRequest) + return + } + name := strings.TrimSpace(r.FormValue("name")) + email := strings.TrimSpace(r.FormValue("email")) + userFPR := strings.TrimSpace(r.FormValue("fingerprint")) // optional override + file, hdr, err := r.FormFile("file") + if err != nil { + http.Error(w, "missing file", http.StatusBadRequest) + return + } + defer file.Close() + var buf bytes.Buffer + lr := io.LimitedReader{R: file, N: maxUploadSize} + if _, err := io.Copy(&buf, &lr); err != nil { + http.Error(w, "read error", http.StatusBadRequest) + return + } + b := buf.Bytes() + if !isASCII(string(b)) || !bytes.Contains(b, []byte("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { + http.Error(w, "file must be ASCII-armored PGP public key (.asc)", http.StatusBadRequest) + return + } + // Parse fingerprint + autoFPR, parseErr := parseFingerprintFromASCII(b) + fpr := userFPR + if fpr == "" && parseErr == nil { + fpr = autoFPR + } + if fpr == "" { + http.Error(w, "could not parse fingerprint; please provide it manually", http.StatusBadRequest) + return + } + fpr = strings.ToUpper(strings.ReplaceAll(fpr, " ", "")) - base := sanitizeFilename(hdr.Filename) - if base == ".asc" || base == "" { - base = sanitizeFilename(email) - } - path := filepath.Join(keysDir, base) - if err := os.WriteFile(path, b, 0o644); err != nil { - http.Error(w, "save error", 500) + base := sanitizeFilename(hdr.Filename) + if base == ".asc" || base == "" { + base = sanitizeFilename(email) + } + path := filepath.Join(keysDir, base) + if err := os.WriteFile(path, b, 0o644); err != nil { + http.Error(w, "save error", 500) + return + } + + rec := KeyRecord{ID: genID(email, fpr), Name: name, Email: email, Fingerprint: fpr, Filename: base, CreatedAt: time.Now()} + if err := st.upsert(rec); err != nil { + http.Error(w, "index error", 500) + return + } + http.Redirect(w, r, "/", http.StatusSeeOther) + } else { + http.Error(w, "method not allowed - WRITEACCESS", http.StatusMethodNotAllowed) return } - rec := KeyRecord{ID: genID(email, fpr), Name: name, Email: email, Fingerprint: fpr, Filename: base, CreatedAt: time.Now()} - if err := st.upsert(rec); err != nil { - http.Error(w, "index error", 500) - return - } - http.Redirect(w, r, "/", http.StatusSeeOther) }) // Serve/download by ID diff --git a/templates/layout.html b/templates/layout.html index 99ac150..170ed90 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -20,7 +20,7 @@