From a8cf6aa54f6a8c5df1f06e2c9476180a99bff63d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20R=C3=B6ger?= <jonas.roeger@tu-dortmund.de>
Date: Sun, 13 Oct 2019 17:52:20 +0200
Subject: [PATCH] Added server side ability for reporting bugs

---
 internal/bugreport.go | 91 +++++++++++++++++++++++++++++++++++++++++++
 internal/server.go    | 46 ++++++++++++++++++++++
 2 files changed, 137 insertions(+)
 create mode 100644 internal/bugreport.go

diff --git a/internal/bugreport.go b/internal/bugreport.go
new file mode 100644
index 0000000..68e5d84
--- /dev/null
+++ b/internal/bugreport.go
@@ -0,0 +1,91 @@
+package wtfd
+
+import (
+	"errors"
+	"fmt"
+	"time"
+	"strconv"
+	"net/smtp"
+)
+
+var (
+	serviceDeskDomain          = "jroeger.de"
+	serviceDeskUser            = "noreply"
+	serviceDeskPort            = 25 // server to server smtp port
+	serviceDeskEnabled         = true
+	rateLimitInterval  float64 = 180 // 3 Minutes
+	rateLimitReports           = 2   // 2 Reports during interval before beeing rate limited
+
+	userAccess map[string]access = make(map[string]access)
+)
+
+type access struct {
+	lastBlock  time.Time // Currently unused
+	lastAccess []time.Time
+}
+
+/**
+ * Check if user is rate limited
+ */
+func BRIsUserRateLimited(u *User) bool {
+	record, ok := userAccess[u.Name]
+	if !ok {
+		return false
+	}
+
+	/* Ok if no critical ammount of records */
+	if len(record.lastAccess) < rateLimitReports {
+		return false
+	}
+
+	/* Check if earliest record is in interval, then block */
+	if time.Since(record.lastAccess[0]).Seconds() < rateLimitInterval {
+		return true
+	}
+
+	return false
+}
+
+/**
+ * Register a user access
+ */
+func registerUserAccess(u *User) {
+	record, ok := userAccess[u.Name]
+
+	if !ok {
+		/* New record */
+		record = access{
+			lastBlock:  time.Time{},
+			lastAccess: []time.Time{time.Now()},
+		}
+	} else if len(record.lastAccess) < rateLimitReports {
+		/* No critical ammount of records */
+		record.lastAccess = append(record.lastAccess, time.Now())
+	} else if !BRIsUserRateLimited(u) {
+		/* Cycle access */
+		record.lastAccess = record.lastAccess[1:]
+		record.lastAccess = append(record.lastAccess, time.Now())
+	}
+	userAccess[u.Name] = record
+}
+
+/**
+ * Send bugreport
+ */
+func BRDispatchBugreport(u *User, subject string, content string) error {
+	if !serviceDeskEnabled {
+		return errors.New("Service Desk is disabled")
+	}
+
+	recipient := serviceDeskUser + "@" + serviceDeskDomain
+	recipients := []string{recipient}
+	formatContent := fmt.Sprintf("From: %s\nSubject: %s\n\n%s", u.Name, subject, content)
+
+
+	err := smtp.SendMail(serviceDeskDomain+":"+strconv.Itoa(serviceDeskPort),
+		nil, u.Name, recipients, []byte(formatContent))
+	if err == nil {
+		registerUserAccess(u)
+	}
+	return err
+}
diff --git a/internal/server.go b/internal/server.go
index 3f709fc..18c0d36 100644
--- a/internal/server.go
+++ b/internal/server.go
@@ -300,6 +300,51 @@ func logout(w http.ResponseWriter, r *http.Request) {
 
 }
 
+func reportBug(w http.ResponseWriter, r *http.Request) {
+	var err error
+
+	if r.Method != "POST" {
+		w.WriteHeader(http.StatusBadRequest)
+		_, _ = fmt.Fprintf(w, "Invalid Request")
+		return
+	}
+
+	/* Check user login */
+	user, ok := getUser(r)
+	if !ok {
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = fmt.Fprintf(w, "Server Error: %v", "Not logged in")
+		return
+	}
+
+	/* Check if user is rate limited */
+	if BRIsUserRateLimited(&user) {
+		w.WriteHeader(http.StatusTooManyRequests)
+		_, _ = fmt.Fprint(w, "Too many requsets")
+		return
+	}
+
+	/* Read and Check form */
+	if err = r.ParseForm() ; err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = fmt.Fprintf(w, "Server Error: %v", "Not logged in")
+		return
+	}
+	subject := r.FormValue("subject")
+	content := r.FormValue("content")
+	if subject == "" || content == "" {
+		w.WriteHeader(http.StatusBadRequest)
+		fmt.Fprint(w, "Invaild Request")
+		return
+	}
+
+	/* Try to dispatch bugreport */
+	if err = BRDispatchBugreport(&user, subject , content); err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		_, _ = fmt.Fprintf(w, "Server Error: %v", err)
+	}
+}
+
 func solutionview(w http.ResponseWriter, r *http.Request) {
 	vars := mux.Vars(r)
 	chall, err := challs.Find(vars["chall"])
@@ -493,6 +538,7 @@ func Server() error {
 	r.HandleFunc("/register", register)
 	r.HandleFunc("/submitflag", submitFlag)
 	r.HandleFunc("/ws", leaderboardWS)
+	r.HandleFunc("/reportbug", reportBug)
 	r.HandleFunc("/{chall}", mainpage)
 	r.HandleFunc("/detailview/{chall}", detailview)
 	r.HandleFunc("/solutionview/{chall}", solutionview)
-- 
GitLab