diff --git a/mattermost/server/bathroom-linux-amd64 b/mattermost/server/bathroom-linux-amd64 index b43b3ed..681e00f 100755 --- a/mattermost/server/bathroom-linux-amd64 +++ b/mattermost/server/bathroom-linux-amd64 Binary files differ diff --git a/mattermost/server/bathroom-linux-amd64 b/mattermost/server/bathroom-linux-amd64 index b43b3ed..681e00f 100755 --- a/mattermost/server/bathroom-linux-amd64 +++ b/mattermost/server/bathroom-linux-amd64 Binary files differ diff --git a/mattermost/server/bathroom.go b/mattermost/server/bathroom.go index 52bafbe..225a000 100644 --- a/mattermost/server/bathroom.go +++ b/mattermost/server/bathroom.go @@ -24,18 +24,21 @@ "encoding/pem" "crypto/rsa" "crypto/x509" + "crypto/rand" + "crypto/sha1" "path" + "encoding/base64" ) var userSplit *regexp.Regexp = regexp.MustCompile(`\s+(^|[^,])|\s*,\s*`) const ( - Unknown = iota + Unknown = uint(iota) Open Closed ) -func statusName(status int) (string, error) { +func statusName(status uint) (string, error) { switch status { case Unknown: return "unknown", nil @@ -50,13 +53,14 @@ type DoorRequest struct { ip string - time int + time int64 verify string + status uint } type Door struct { id uint8 - status int + status uint pubKey *rsa.PublicKey lastRequest *DoorRequest } @@ -135,10 +139,7 @@ } } -func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status int, report bool) error { - p.doorLock.Lock() - defer p.doorLock.Unlock() - +func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status uint, report bool) error { if id < 1 || id > p.config.numDoors { return errors.New(fmt.Sprintf("Invalid door id %d", id)) } @@ -179,7 +180,32 @@ return p; } +func (p *BathroomMonitorPlugin) validateRequestDoorId(r *http.Request) (uint8, error) { + p.configLock.Lock() + numDoors := p.config.numDoors + p.configLock.Unlock() + + + doorVal, ok := r.Form["door_id"] + if !ok || len(doorVal) == 0 { + return 0, errors.New("Please send door id") + } + + doorId, err := strconv.ParseUint(doorVal[0], 10, 8) + if err != nil { + return 0, errors.New(fmt.Sprintf("Couldn't parse door_id: %s", err.Error())) + } + doorId8 := uint8(doorId) + + if doorId8 < 1 || doorId8 > numDoors { + return 0, errors.New("Invalid door id") + } + + return doorId8, nil +} + func (p *BathroomMonitorPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { + p.API.LogInfo(fmt.Sprintf("Requested path: %s", r.URL.Path)) if r.URL.Path == "/status" { p.doorLock.Lock() defer p.doorLock.Unlock() @@ -197,6 +223,125 @@ return } if r.URL.Path == "/status-update" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + statusVal, ok := r.Form["status"] + if !ok || len(statusVal) == 0 { + fmt.Fprint(w, "Please send door status") + return + } + + status, err := strconv.ParseUint(statusVal[0], 10, 8) + if err != nil { + fmt.Fprintf(w, "Couldn't parse status: %s", err.Error()) + return + } + + if status != 0 && status != 1 { + fmt.Fprint(w, "Invalid status") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].pubKey == nil { + fmt.Fprintf(w, "No public key found for door %d", doorId8) + return + } + + p.API.LogInfo("Getting random bytes") + var verifyBytes [50]byte + rand.Read(verifyBytes[:]) + p.API.LogInfo("Encoding random bytes") + + verifyB64 := base64.StdEncoding.EncodeToString(verifyBytes[:]) + + p.API.LogInfo("Encryping random bytes") + encrypted, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, p.doors[doorId8 - 1].pubKey, verifyBytes[:], []byte{}) + if err != nil { + fmt.Fprintf(w, "Couldn't encrypt verification: %s", err.Error()) + return + } + + p.API.LogInfo("Encoding encrypted bytes") + encryptedB64 := base64.StdEncoding.EncodeToString(encrypted) + + + req := &DoorRequest { + ip:r.RemoteAddr, + time:time.Now().Unix(), + verify:verifyB64, + status: uint(status), + } + + p.doors[doorId8 - 1].lastRequest = req + + p.API.LogInfo(fmt.Sprintf("NEW REQUEST SAVED: %s %d %s", req.ip, req.time, req.verify)) + + fmt.Fprint(w, encryptedB64) + + + return + } + if r.URL.Path == "/verify-status" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + verifyB64, ok := r.Form["verify"] + if !ok || len(verifyB64) <= 0 { + fmt.Fprint(w, "Please send the verification code") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].lastRequest == nil { + fmt.Fprint(w, "Invalid request") + return + } + + req := p.doors[doorId8 - 1].lastRequest + if req.ip != r.RemoteAddr { + fmt.Fprintf(w, "Not your request %s %s", req.ip, r.RemoteAddr) + return + } + + diff := time.Now().Unix() - req.time + if diff < 0 || diff > 10 { + fmt.Fprint(w, "Request expired") + return + } + + + if req.verify != verifyB64[0] { + fmt.Fprint(w, "Unauthorized request") + return + } + + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + p.doors[doorId8 - 1].lastRequest = nil + + p.setDoorStatus(doorId8, req.status, true) + + return + } http.NotFound(w, r) } @@ -396,7 +541,7 @@ continue } - statusInt, err := strconv.ParseInt(status, 10, 32) + statusInt, err := strconv.ParseUint(status, 10, 32) if err != nil { p.API.LogError(errors.Wrap(err, "Status invalid " + status).Error()) continue @@ -407,7 +552,9 @@ newStatus = Closed } + p.doorLock.Lock() err = p.setDoorStatus(id, newStatus, true) + p.doorLock.Unlock() if err != nil { p.API.LogError(errors.Wrap(err, fmt.Sprintf("Couldn't set door status %d %d", id, statusInt)).Error()) diff --git a/mattermost/server/bathroom-linux-amd64 b/mattermost/server/bathroom-linux-amd64 index b43b3ed..681e00f 100755 --- a/mattermost/server/bathroom-linux-amd64 +++ b/mattermost/server/bathroom-linux-amd64 Binary files differ diff --git a/mattermost/server/bathroom.go b/mattermost/server/bathroom.go index 52bafbe..225a000 100644 --- a/mattermost/server/bathroom.go +++ b/mattermost/server/bathroom.go @@ -24,18 +24,21 @@ "encoding/pem" "crypto/rsa" "crypto/x509" + "crypto/rand" + "crypto/sha1" "path" + "encoding/base64" ) var userSplit *regexp.Regexp = regexp.MustCompile(`\s+(^|[^,])|\s*,\s*`) const ( - Unknown = iota + Unknown = uint(iota) Open Closed ) -func statusName(status int) (string, error) { +func statusName(status uint) (string, error) { switch status { case Unknown: return "unknown", nil @@ -50,13 +53,14 @@ type DoorRequest struct { ip string - time int + time int64 verify string + status uint } type Door struct { id uint8 - status int + status uint pubKey *rsa.PublicKey lastRequest *DoorRequest } @@ -135,10 +139,7 @@ } } -func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status int, report bool) error { - p.doorLock.Lock() - defer p.doorLock.Unlock() - +func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status uint, report bool) error { if id < 1 || id > p.config.numDoors { return errors.New(fmt.Sprintf("Invalid door id %d", id)) } @@ -179,7 +180,32 @@ return p; } +func (p *BathroomMonitorPlugin) validateRequestDoorId(r *http.Request) (uint8, error) { + p.configLock.Lock() + numDoors := p.config.numDoors + p.configLock.Unlock() + + + doorVal, ok := r.Form["door_id"] + if !ok || len(doorVal) == 0 { + return 0, errors.New("Please send door id") + } + + doorId, err := strconv.ParseUint(doorVal[0], 10, 8) + if err != nil { + return 0, errors.New(fmt.Sprintf("Couldn't parse door_id: %s", err.Error())) + } + doorId8 := uint8(doorId) + + if doorId8 < 1 || doorId8 > numDoors { + return 0, errors.New("Invalid door id") + } + + return doorId8, nil +} + func (p *BathroomMonitorPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { + p.API.LogInfo(fmt.Sprintf("Requested path: %s", r.URL.Path)) if r.URL.Path == "/status" { p.doorLock.Lock() defer p.doorLock.Unlock() @@ -197,6 +223,125 @@ return } if r.URL.Path == "/status-update" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + statusVal, ok := r.Form["status"] + if !ok || len(statusVal) == 0 { + fmt.Fprint(w, "Please send door status") + return + } + + status, err := strconv.ParseUint(statusVal[0], 10, 8) + if err != nil { + fmt.Fprintf(w, "Couldn't parse status: %s", err.Error()) + return + } + + if status != 0 && status != 1 { + fmt.Fprint(w, "Invalid status") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].pubKey == nil { + fmt.Fprintf(w, "No public key found for door %d", doorId8) + return + } + + p.API.LogInfo("Getting random bytes") + var verifyBytes [50]byte + rand.Read(verifyBytes[:]) + p.API.LogInfo("Encoding random bytes") + + verifyB64 := base64.StdEncoding.EncodeToString(verifyBytes[:]) + + p.API.LogInfo("Encryping random bytes") + encrypted, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, p.doors[doorId8 - 1].pubKey, verifyBytes[:], []byte{}) + if err != nil { + fmt.Fprintf(w, "Couldn't encrypt verification: %s", err.Error()) + return + } + + p.API.LogInfo("Encoding encrypted bytes") + encryptedB64 := base64.StdEncoding.EncodeToString(encrypted) + + + req := &DoorRequest { + ip:r.RemoteAddr, + time:time.Now().Unix(), + verify:verifyB64, + status: uint(status), + } + + p.doors[doorId8 - 1].lastRequest = req + + p.API.LogInfo(fmt.Sprintf("NEW REQUEST SAVED: %s %d %s", req.ip, req.time, req.verify)) + + fmt.Fprint(w, encryptedB64) + + + return + } + if r.URL.Path == "/verify-status" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + verifyB64, ok := r.Form["verify"] + if !ok || len(verifyB64) <= 0 { + fmt.Fprint(w, "Please send the verification code") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].lastRequest == nil { + fmt.Fprint(w, "Invalid request") + return + } + + req := p.doors[doorId8 - 1].lastRequest + if req.ip != r.RemoteAddr { + fmt.Fprintf(w, "Not your request %s %s", req.ip, r.RemoteAddr) + return + } + + diff := time.Now().Unix() - req.time + if diff < 0 || diff > 10 { + fmt.Fprint(w, "Request expired") + return + } + + + if req.verify != verifyB64[0] { + fmt.Fprint(w, "Unauthorized request") + return + } + + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + p.doors[doorId8 - 1].lastRequest = nil + + p.setDoorStatus(doorId8, req.status, true) + + return + } http.NotFound(w, r) } @@ -396,7 +541,7 @@ continue } - statusInt, err := strconv.ParseInt(status, 10, 32) + statusInt, err := strconv.ParseUint(status, 10, 32) if err != nil { p.API.LogError(errors.Wrap(err, "Status invalid " + status).Error()) continue @@ -407,7 +552,9 @@ newStatus = Closed } + p.doorLock.Lock() err = p.setDoorStatus(id, newStatus, true) + p.doorLock.Unlock() if err != nil { p.API.LogError(errors.Wrap(err, fmt.Sprintf("Couldn't set door status %d %d", id, statusInt)).Error()) diff --git a/mattermost/server/bathroom.tar.gz b/mattermost/server/bathroom.tar.gz index a51c02d..f2298e9 100644 --- a/mattermost/server/bathroom.tar.gz +++ b/mattermost/server/bathroom.tar.gz Binary files differ diff --git a/mattermost/server/bathroom-linux-amd64 b/mattermost/server/bathroom-linux-amd64 index b43b3ed..681e00f 100755 --- a/mattermost/server/bathroom-linux-amd64 +++ b/mattermost/server/bathroom-linux-amd64 Binary files differ diff --git a/mattermost/server/bathroom.go b/mattermost/server/bathroom.go index 52bafbe..225a000 100644 --- a/mattermost/server/bathroom.go +++ b/mattermost/server/bathroom.go @@ -24,18 +24,21 @@ "encoding/pem" "crypto/rsa" "crypto/x509" + "crypto/rand" + "crypto/sha1" "path" + "encoding/base64" ) var userSplit *regexp.Regexp = regexp.MustCompile(`\s+(^|[^,])|\s*,\s*`) const ( - Unknown = iota + Unknown = uint(iota) Open Closed ) -func statusName(status int) (string, error) { +func statusName(status uint) (string, error) { switch status { case Unknown: return "unknown", nil @@ -50,13 +53,14 @@ type DoorRequest struct { ip string - time int + time int64 verify string + status uint } type Door struct { id uint8 - status int + status uint pubKey *rsa.PublicKey lastRequest *DoorRequest } @@ -135,10 +139,7 @@ } } -func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status int, report bool) error { - p.doorLock.Lock() - defer p.doorLock.Unlock() - +func (p *BathroomMonitorPlugin) setDoorStatus(id uint8, status uint, report bool) error { if id < 1 || id > p.config.numDoors { return errors.New(fmt.Sprintf("Invalid door id %d", id)) } @@ -179,7 +180,32 @@ return p; } +func (p *BathroomMonitorPlugin) validateRequestDoorId(r *http.Request) (uint8, error) { + p.configLock.Lock() + numDoors := p.config.numDoors + p.configLock.Unlock() + + + doorVal, ok := r.Form["door_id"] + if !ok || len(doorVal) == 0 { + return 0, errors.New("Please send door id") + } + + doorId, err := strconv.ParseUint(doorVal[0], 10, 8) + if err != nil { + return 0, errors.New(fmt.Sprintf("Couldn't parse door_id: %s", err.Error())) + } + doorId8 := uint8(doorId) + + if doorId8 < 1 || doorId8 > numDoors { + return 0, errors.New("Invalid door id") + } + + return doorId8, nil +} + func (p *BathroomMonitorPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { + p.API.LogInfo(fmt.Sprintf("Requested path: %s", r.URL.Path)) if r.URL.Path == "/status" { p.doorLock.Lock() defer p.doorLock.Unlock() @@ -197,6 +223,125 @@ return } if r.URL.Path == "/status-update" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + statusVal, ok := r.Form["status"] + if !ok || len(statusVal) == 0 { + fmt.Fprint(w, "Please send door status") + return + } + + status, err := strconv.ParseUint(statusVal[0], 10, 8) + if err != nil { + fmt.Fprintf(w, "Couldn't parse status: %s", err.Error()) + return + } + + if status != 0 && status != 1 { + fmt.Fprint(w, "Invalid status") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].pubKey == nil { + fmt.Fprintf(w, "No public key found for door %d", doorId8) + return + } + + p.API.LogInfo("Getting random bytes") + var verifyBytes [50]byte + rand.Read(verifyBytes[:]) + p.API.LogInfo("Encoding random bytes") + + verifyB64 := base64.StdEncoding.EncodeToString(verifyBytes[:]) + + p.API.LogInfo("Encryping random bytes") + encrypted, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, p.doors[doorId8 - 1].pubKey, verifyBytes[:], []byte{}) + if err != nil { + fmt.Fprintf(w, "Couldn't encrypt verification: %s", err.Error()) + return + } + + p.API.LogInfo("Encoding encrypted bytes") + encryptedB64 := base64.StdEncoding.EncodeToString(encrypted) + + + req := &DoorRequest { + ip:r.RemoteAddr, + time:time.Now().Unix(), + verify:verifyB64, + status: uint(status), + } + + p.doors[doorId8 - 1].lastRequest = req + + p.API.LogInfo(fmt.Sprintf("NEW REQUEST SAVED: %s %d %s", req.ip, req.time, req.verify)) + + fmt.Fprint(w, encryptedB64) + + + return + } + if r.URL.Path == "/verify-status" { + r.ParseForm() + p.API.LogInfo("Contacted by " + r.RemoteAddr) + doorId8, err := p.validateRequestDoorId(r) + if err != nil { + fmt.Fprint(w, err.Error()) + return + } + + verifyB64, ok := r.Form["verify"] + if !ok || len(verifyB64) <= 0 { + fmt.Fprint(w, "Please send the verification code") + return + } + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + if p.doors[doorId8 - 1].lastRequest == nil { + fmt.Fprint(w, "Invalid request") + return + } + + req := p.doors[doorId8 - 1].lastRequest + if req.ip != r.RemoteAddr { + fmt.Fprintf(w, "Not your request %s %s", req.ip, r.RemoteAddr) + return + } + + diff := time.Now().Unix() - req.time + if diff < 0 || diff > 10 { + fmt.Fprint(w, "Request expired") + return + } + + + if req.verify != verifyB64[0] { + fmt.Fprint(w, "Unauthorized request") + return + } + + + p.doorLock.Lock() + defer p.doorLock.Unlock() + + p.doors[doorId8 - 1].lastRequest = nil + + p.setDoorStatus(doorId8, req.status, true) + + return + } http.NotFound(w, r) } @@ -396,7 +541,7 @@ continue } - statusInt, err := strconv.ParseInt(status, 10, 32) + statusInt, err := strconv.ParseUint(status, 10, 32) if err != nil { p.API.LogError(errors.Wrap(err, "Status invalid " + status).Error()) continue @@ -407,7 +552,9 @@ newStatus = Closed } + p.doorLock.Lock() err = p.setDoorStatus(id, newStatus, true) + p.doorLock.Unlock() if err != nil { p.API.LogError(errors.Wrap(err, fmt.Sprintf("Couldn't set door status %d %d", id, statusInt)).Error()) diff --git a/mattermost/server/bathroom.tar.gz b/mattermost/server/bathroom.tar.gz index a51c02d..f2298e9 100644 --- a/mattermost/server/bathroom.tar.gz +++ b/mattermost/server/bathroom.tar.gz Binary files differ diff --git a/mattermost/server/plugin.json b/mattermost/server/plugin.json index 6158eed..e4d22cb 100644 --- a/mattermost/server/plugin.json +++ b/mattermost/server/plugin.json @@ -7,7 +7,7 @@ "settings_schema": { "settings": [ {"key":"NumDoors", "display_name":"Number of Door Sensors", "type":"text", "default":"1", "help_text":"How many Pis"}, - {"key":"WatchPath", "display_name":"Pi status folder", "type":"text", "default":"./", "help_text":"Path to watch for doorX files updated from PHP"}, + {"key":"KeyPath", "display_name":"", "type":"text", "default":"./", "help_text":"Path to watch for doorX files updated from PHP"}, {"key":"AdminUsers", "display_name":"Plugin admin users", "type":"text", "default":"", "help_text":"Space- or comma-separated list of users to notify with plugin debug message"} ] }