From 42e6157238a45cad0c68f7c9f7932c0f8b80237c Mon Sep 17 00:00:00 2001 From: Maxim Baz Date: Sat, 14 Apr 2018 12:49:03 +0200 Subject: [PATCH] Implement parsing browser requests and sending errors (#9) --- Makefile | 1 + PROTOCOL.md | 8 +++-- errors/errors.go | 26 ++++++++++++++++ main.go | 27 ++++++++--------- persistentlog/syslog.go | 8 ++--- persistentlog/windows.go | 2 +- process.go | 5 ---- request/request.go | 63 ++++++++++++++++++++++++++++++++++++++ response/response.go | 65 ++++++++++++++++++++++++++++++++++++++++ version/version.go | 15 ++++++++++ 10 files changed, 192 insertions(+), 28 deletions(-) create mode 100644 errors/errors.go delete mode 100644 process.go create mode 100644 request/request.go create mode 100644 response/response.go create mode 100644 version/version.go diff --git a/Makefile b/Makefile index 2df7eca..0c88414 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +.PHONY: all all: deps browserpass test .PHONY: deps diff --git a/PROTOCOL.md b/PROTOCOL.md index d857841..f072d6f 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -41,9 +41,11 @@ should be supplied as a `message` parameter. ## List of Error Codes -| Code | Description | Parameters | -| ---- | ----------- | ---------- | -| | | | +| Code | Description | Parameters | +| ---- | -------------------------------------- | ---------- | +| 10 | Unable to parse browser request length | | +| 11 | Unable to parse browser request | | +| 12 | Invalid request action | action | ## Settings diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..93e0c62 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,26 @@ +package errors + +import ( + "os" +) + +// Code exit code +type Code int + +// Error codes that are sent to the browser extension and used as exit codes in the app. +// DO NOT MODIFY THE VALUES, always append new error codes to the bottom. +const ( + // CodeParseRequestLength error parsing request length + CodeParseRequestLength Code = 10 + + // CodeParseRequest error parsing request + CodeParseRequest Code = 11 + + // CodeInvalidRequestAction error parsing request action + CodeInvalidRequestAction = 12 +) + +// ExitWithCode exit with error code +func ExitWithCode(code Code) { + os.Exit(int(code)) +} diff --git a/main.go b/main.go index 222ba7e..9fe05c0 100644 --- a/main.go +++ b/main.go @@ -5,35 +5,34 @@ import ( "fmt" "os" - "github.com/maximbaz/browserpass-native/openbsd" - "github.com/maximbaz/browserpass-native/persistentlog" + "github.com/browserpass/browserpass-native/openbsd" + "github.com/browserpass/browserpass-native/persistentlog" + "github.com/browserpass/browserpass-native/request" + "github.com/browserpass/browserpass-native/version" log "github.com/sirupsen/logrus" ) -// VERSION host app version -const VERSION = "3.0.0" - func main() { - var verbose bool - var version bool - flag.BoolVar(&verbose, "v", false, "print verbose output") - flag.BoolVar(&version, "version", false, "print version and exit") + var isVerbose bool + var isVersion bool + flag.BoolVar(&isVerbose, "v", false, "print verbose output") + flag.BoolVar(&isVersion, "version", false, "print version and exit") flag.Parse() - if version { - fmt.Println("Browserpass host app version:", VERSION) + if isVersion { + fmt.Println("Browserpass host app version:", version.String()) os.Exit(0) } openbsd.Pledge("stdio rpath proc exec") log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) - if verbose { + if isVerbose { log.SetLevel(log.DebugLevel) } persistentlog.AddPersistentLogHook() - log.Debugf("Starting browserpass host app v%v", VERSION) - process() + log.Debugf("Starting browserpass host app v%v", version.String()) + request.Process() } diff --git a/persistentlog/syslog.go b/persistentlog/syslog.go index d56dfdc..2694330 100644 --- a/persistentlog/syslog.go +++ b/persistentlog/syslog.go @@ -11,11 +11,9 @@ import ( // AddPersistentLogHook configures persisting logs in syslog func AddPersistentLogHook() { - hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass") - - if err == nil { - log.AddHook(hook) + if hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass"); err != nil { + log.Warn("Unable to connect to syslog, logs will NOT be persisted: ", err) } else { - log.Warn("Unable to connect to syslog, logs will NOT be persisted") + log.AddHook(hook) } } diff --git a/persistentlog/windows.go b/persistentlog/windows.go index 4485841..d74f75a 100644 --- a/persistentlog/windows.go +++ b/persistentlog/windows.go @@ -19,7 +19,7 @@ func AddPersistentLogHook() { } logFolderPath := filepath.Join(appDataPath, "browserpass") if err := os.MkdirAll(logFolderPath, os.ModePerm); err != nil { - log.Warn("Unable to create browserpass folder in %%APPDATA%%, logs will NOT be persisted") + log.Warn("Unable to create browserpass folder in %%APPDATA%%, logs will NOT be persisted: ", err) return } logFilePath := filepath.Join(logFolderPath, "browserpass.log") diff --git a/process.go b/process.go deleted file mode 100644 index 5a04935..0000000 --- a/process.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func process() { - // read stdin, process request, print to stdout... -} diff --git a/request/request.go b/request/request.go new file mode 100644 index 0000000..8411624 --- /dev/null +++ b/request/request.go @@ -0,0 +1,63 @@ +package request + +import ( + "encoding/binary" + "encoding/json" + "io" + "os" + + "github.com/browserpass/browserpass-native/errors" + "github.com/browserpass/browserpass-native/response" + log "github.com/sirupsen/logrus" +) + +type request struct { + Action string `json:"action"` + Settings interface{} `json:"settings"` +} + +// Process handles browser request +func Process() { + requestLength := parseRequestLength() + request := parseRequest(requestLength) + + switch request.Action { + case "configure": + break + case "list": + break + case "fetch": + break + default: + log.Errorf("Received a browser request with an unknown action: %+v", request) + response.SendError(errors.CodeInvalidRequestAction, "Invalid request action", &map[string]string{"action": request.Action}) + errors.ExitWithCode(errors.CodeInvalidRequestAction) + } +} + +// Request length is the first 4 bytes in LittleEndian encoding on stdin +func parseRequestLength() uint32 { + var length uint32 + if err := binary.Read(os.Stdin, binary.LittleEndian, &length); err != nil { + // TODO: Original browserpass ignores EOF as if it is expected, is it true? + // if err == io.EOF { + // return + // } + log.Error("Unable to parse the length of the browser request: ", err) + response.SendError(errors.CodeParseRequestLength, "Unable to parse the length of the browser request", nil) + errors.ExitWithCode(errors.CodeParseRequestLength) + } + return length +} + +// Request is a json with a predefined structure +func parseRequest(messageLength uint32) request { + var parsed request + reader := &io.LimitedReader{R: os.Stdin, N: int64(messageLength)} + if err := json.NewDecoder(reader).Decode(&parsed); err != nil { + log.Error("Unable to parse the browser request: ", err) + response.SendError(errors.CodeParseRequest, "Unable to parse the browser request", nil) + errors.ExitWithCode(errors.CodeParseRequest) + } + return parsed +} diff --git a/response/response.go b/response/response.go new file mode 100644 index 0000000..1171ba7 --- /dev/null +++ b/response/response.go @@ -0,0 +1,65 @@ +package response + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "os" + + "github.com/browserpass/browserpass-native/errors" + "github.com/browserpass/browserpass-native/version" + log "github.com/sirupsen/logrus" +) + +type okResponse struct { + Status string `json:"status"` + Version int `json:"version"` + Data interface{} `json:"data"` +} + +type errorResponse struct { + Status string `json:"status"` + Code errors.Code `json:"code"` + Version int `json:"version"` + Params interface{} `json:"params"` +} + +// SendError sends an error response to the browser extension in the predefined json format +func SendError(errorCode errors.Code, errorMsg string, extraParams *map[string]string) { + params := map[string]string{ + "message": errorMsg, + } + if extraParams != nil { + for key, value := range *extraParams { + params[key] = value + } + } + send(&errorResponse{ + Status: "error", + Code: errorCode, + Version: version.Code, + Params: params, + }) +} + +func send(data interface{}) { + switch data.(type) { + case *okResponse: + case *errorResponse: + break + default: + log.Fatalf("Only data of type OkResponse and ErrorResponse is allowed to be sent to the browser extension, attempted to send: %+v", data) + } + + var bytesBuffer bytes.Buffer + if err := json.NewEncoder(&bytesBuffer).Encode(data); err != nil { + log.Fatal("Unable to encode data for sending: ", err) + } + + if err := binary.Write(os.Stdout, binary.LittleEndian, uint32(bytesBuffer.Len())); err != nil { + log.Fatal("Unable to send the length of the response data: ", err) + } + if _, err := bytesBuffer.WriteTo(os.Stdout); err != nil { + log.Fatal("Unable to send the response data: ", err) + } +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..54f19c1 --- /dev/null +++ b/version/version.go @@ -0,0 +1,15 @@ +package version + +import "fmt" + +const major = 3 +const minor = 0 +const patch = 0 + +// Code version as integer +const Code = major*1000000 + minor*1000 + patch + +// String version as string +func String() string { + return fmt.Sprintf("%d.%d.%d", major, minor, patch) +}