From 5458c3ba86ae518c79dd97d57fea317975e3ffe0 Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Sun, 21 Jan 2024 11:39:27 +0200 Subject: [PATCH] v0.1.0 --- handlers.go | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 178 -------------------------------------------------- 2 files changed, 185 insertions(+), 178 deletions(-) create mode 100644 handlers.go diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..3780b84 --- /dev/null +++ b/handlers.go @@ -0,0 +1,185 @@ +package main + +import ( + "crypto/sha256" + "crypto/subtle" + "encoding/json" + "log" + "net/http" + "strconv" + "strings" +) + +// HTTP Basic Authentication middleware for http.HandlerFunc +// This is used for the API +func authHandlerFunc(next http.HandlerFunc) http.HandlerFunc { + // Based on https://www.alexedwards.net/blog/basic-authentication-in-go + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if config.DisableAuth { + next.ServeHTTP(w, r) + return + } + user, pass, ok := r.BasicAuth() + if ok { + userHash := sha256.Sum256([]byte(user)) + passHash := sha256.Sum256([]byte(pass)) + usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1) + passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1) + if usernameMatch && passwordMatch { + next.ServeHTTP(w, r) + return + } + } + if len(user) == 0 { + user = "-" + } + log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user) + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + }) +} + +// HTTP Basic Authentication middleware for http.Handler +// Used for the HTML monitor views +func authHandler(next http.Handler) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if config.DisableAuth { + next.ServeHTTP(w, r) + return + } + user, pass, ok := r.BasicAuth() + if ok { + userHash := sha256.Sum256([]byte(user)) + passHash := sha256.Sum256([]byte(pass)) + usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1) + passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1) + if usernameMatch && passwordMatch { + next.ServeHTTP(w, r) + return + } + } + if len(user) == 0 { + user = "-" + } + log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user) + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + }) +} + +// /api/v1/coils endpoint +func coils(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/coils/"), "/") + if len(pathparams[0]) == 0 { + _ = json.NewEncoder(w).Encode(device.Coils) + } else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST" + intaddr, err := strconv.Atoi(pathparams[0]) + if err != nil { + log.Println("ERROR: Could not parse coil address", pathparams[0]) + log.Println(err) + return + } + err = device.ReadCoil(uint16(intaddr)) + if err != nil { + log.Println("ERROR ReadCoil: client.ReadCoils: ", err) + } + _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) + } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 { + intaddr, err := strconv.Atoi(pathparams[0]) + if err != nil { + log.Println("ERROR: Could not parse coil address", pathparams[0]) + log.Println(err) + return + } + boolval, err := strconv.ParseBool(pathparams[1]) + if err != nil { + log.Println("ERROR: Could not parse coil value", pathparams[1]) + log.Println(err) + return + } + if config.ReadOnly { + log.Println("WARNING: Read only mode, refusing to write to device") + } else { + device.WriteCoil(uint16(intaddr), boolval) + } + _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) + } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 { + intaddr, err := strconv.Atoi(pathparams[0]) + if err != nil { + log.Println("ERROR: Could not parse coil address", pathparams[0]) + log.Println(err) + return + } + if config.ReadOnly { + log.Println("WARNING: Read only mode, refusing to write to device") + } else { + device.WriteCoil(uint16(intaddr), !device.Coils[intaddr].Value) + } + _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) + } +} + +// /api/v1/registers endpoint +func registers(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/registers/"), "/") + if len(pathparams[0]) == 0 { + _ = json.NewEncoder(w).Encode(device.Registers) + } else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST" + intaddr, err := strconv.Atoi(pathparams[0]) + if err != nil { + log.Println("ERROR: Could not parse register address", pathparams[0]) + log.Println(err) + return + } + _, err = device.ReadRegister(uint16(intaddr)) + if err != nil { + log.Println("ERROR: ReadRegister:", err) + } + _ = json.NewEncoder(w).Encode(device.Registers[intaddr]) + } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 { + intaddr, err := strconv.Atoi(pathparams[0]) + if err != nil { + log.Println("ERROR: Could not parse register address", pathparams[0]) + log.Println(err) + return + } + intval, err := strconv.Atoi(pathparams[1]) + if err != nil { + log.Println("ERROR: Could not parse register value", pathparams[1]) + log.Println(err) + return + } + if config.ReadOnly { + log.Println("WARNING: Read only mode, refusing to write to device") + } else { + _, err = device.WriteRegister(uint16(intaddr), uint16(intval)) + if err != nil { + log.Println(err) + } + } + _ = json.NewEncoder(w).Encode(device.Registers[intaddr]) + } +} + +// /status endpoint +func status(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(device.Status) +} + +// /api/v1/temperature endpoint +func temperature(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/temperature/"), "/") + if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 { + err := device.Temperature(pathparams[0]) + if err != nil { + log.Println("ERROR: ", err) + } + _ = json.NewEncoder(w).Encode(device.Registers[135]) + } else { + return + } +} diff --git a/main.go b/main.go index 59d9c96..785a813 100644 --- a/main.go +++ b/main.go @@ -2,16 +2,12 @@ package main import ( "crypto/sha256" - "crypto/subtle" "embed" - "encoding/json" "flag" "io/fs" "log" "net/http" "os" - "strconv" - "strings" "time" "github.com/0ranki/enervent-ctrl/pingvin" @@ -52,180 +48,6 @@ type Conf struct { ReadOnly bool `yaml:"read_only"` } -// HTTP Basic Authentication middleware for http.HandlerFunc -// This is used for the API -func authHandlerFunc(next http.HandlerFunc) http.HandlerFunc { - // Based on https://www.alexedwards.net/blog/basic-authentication-in-go - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if config.DisableAuth { - next.ServeHTTP(w, r) - return - } - user, pass, ok := r.BasicAuth() - if ok { - userHash := sha256.Sum256([]byte(user)) - passHash := sha256.Sum256([]byte(pass)) - usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1) - passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1) - if usernameMatch && passwordMatch { - next.ServeHTTP(w, r) - return - } - } - if len(user) == 0 { - user = "-" - } - log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user) - w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) - http.Error(w, "Unauthorized", http.StatusUnauthorized) - }) -} - -// HTTP Basic Authentication middleware for http.Handler -// Used for the HTML monitor views -func authHandler(next http.Handler) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if config.DisableAuth { - next.ServeHTTP(w, r) - return - } - user, pass, ok := r.BasicAuth() - if ok { - userHash := sha256.Sum256([]byte(user)) - passHash := sha256.Sum256([]byte(pass)) - usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1) - passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1) - if usernameMatch && passwordMatch { - next.ServeHTTP(w, r) - return - } - } - if len(user) == 0 { - user = "-" - } - log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user) - w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) - http.Error(w, "Unauthorized", http.StatusUnauthorized) - }) -} - -// \/api/v1/coils endpoint -func coils(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/coils/"), "/") - if len(pathparams[0]) == 0 { - _ = json.NewEncoder(w).Encode(device.Coils) - } else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST" - intaddr, err := strconv.Atoi(pathparams[0]) - if err != nil { - log.Println("ERROR: Could not parse coil address", pathparams[0]) - log.Println(err) - return - } - err = device.ReadCoil(uint16(intaddr)) - if err != nil { - log.Println("ERROR ReadCoil: client.ReadCoils: ", err) - } - _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) - } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 { - intaddr, err := strconv.Atoi(pathparams[0]) - if err != nil { - log.Println("ERROR: Could not parse coil address", pathparams[0]) - log.Println(err) - return - } - boolval, err := strconv.ParseBool(pathparams[1]) - if err != nil { - log.Println("ERROR: Could not parse coil value", pathparams[1]) - log.Println(err) - return - } - if config.ReadOnly { - log.Println("WARNING: Read only mode, refusing to write to device") - } else { - device.WriteCoil(uint16(intaddr), boolval) - } - _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) - } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 { - intaddr, err := strconv.Atoi(pathparams[0]) - if err != nil { - log.Println("ERROR: Could not parse coil address", pathparams[0]) - log.Println(err) - return - } - if config.ReadOnly { - log.Println("WARNING: Read only mode, refusing to write to device") - } else { - device.WriteCoil(uint16(intaddr), !device.Coils[intaddr].Value) - } - _ = json.NewEncoder(w).Encode(device.Coils[intaddr]) - } -} - -// \/api/v1/registers endpoint -func registers(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/registers/"), "/") - if len(pathparams[0]) == 0 { - _ = json.NewEncoder(w).Encode(device.Registers) - } else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST" - intaddr, err := strconv.Atoi(pathparams[0]) - if err != nil { - log.Println("ERROR: Could not parse register address", pathparams[0]) - log.Println(err) - return - } - _, err = device.ReadRegister(uint16(intaddr)) - if err != nil { - log.Println("ERROR: ReadRegister:", err) - } - _ = json.NewEncoder(w).Encode(device.Registers[intaddr]) - } else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 { - intaddr, err := strconv.Atoi(pathparams[0]) - if err != nil { - log.Println("ERROR: Could not parse register address", pathparams[0]) - log.Println(err) - return - } - intval, err := strconv.Atoi(pathparams[1]) - if err != nil { - log.Println("ERROR: Could not parse register value", pathparams[1]) - log.Println(err) - return - } - if config.ReadOnly { - log.Println("WARNING: Read only mode, refusing to write to device") - } else { - _, err = device.WriteRegister(uint16(intaddr), uint16(intval)) - if err != nil { - log.Println(err) - } - } - _ = json.NewEncoder(w).Encode(device.Registers[intaddr]) - } -} - -// \/status endpoint -func status(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(device.Status) -} - -// \/api/v1/temperature endpoint -func temperature(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/temperature/"), "/") - if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 { - err := device.Temperature(pathparams[0]) - if err != nil { - log.Println("ERROR: ", err) - } - _ = json.NewEncoder(w).Encode(device.Registers[135]) - } else { - return - } -} - // Start the HTTP server func serve(cert, key *string) { log.Println("Starting service")