├── .version
├── debug
├── request-configure.hex.txt
└── README.md
├── .gitignore
├── Dockerfile
├── openbsd
├── generic.go
└── openbsd.go
├── browser-files
├── chromium-policy.json
├── firefox-host.json
└── chromium-host.json
├── .editorconfig
├── go.mod
├── persistentlog
├── unsupported.go
├── syslog.go
└── windows.go
├── version
└── version.go
├── LICENSE
├── request
├── common.go
├── list.go
├── process_test.go
├── process.go
├── tree.go
├── fetch.go
├── delete.go
├── save.go
└── configure.go
├── main.go
├── .github
└── ISSUE_TEMPLATE.md
├── go.sum
├── windows-setup.wxs
├── errors
└── errors.go
├── helpers
└── helpers.go
├── response
└── response.go
├── PROTOCOL.md
├── README.md
└── Makefile
/.version:
--------------------------------------------------------------------------------
1 | 3.1.2
2 |
--------------------------------------------------------------------------------
/debug/request-configure.hex.txt:
--------------------------------------------------------------------------------
1 | {"action": "configure"}
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /browserpass
2 | /browserpass-*
3 | dist
4 | vendor
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:latest
2 |
3 | VOLUME /src
4 | WORKDIR /src
5 |
6 | ENTRYPOINT ["make"]
7 |
--------------------------------------------------------------------------------
/openbsd/generic.go:
--------------------------------------------------------------------------------
1 | // +build !openbsd
2 |
3 | package openbsd
4 |
5 | // Pledge allowed system calls, available only on OpenBSD systems
6 | func Pledge(promises string) {
7 | }
8 |
--------------------------------------------------------------------------------
/browser-files/chromium-policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "ExtensionInstallForcelist": [
3 | "naepdomgkenhinolocfifgehidddafch;https://clients2.google.com/service/update2/crx"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/openbsd/openbsd.go:
--------------------------------------------------------------------------------
1 | // +build openbsd
2 |
3 | package openbsd
4 |
5 | import "golang.org/x/sys/unix"
6 |
7 | // Pledge allowed system calls
8 | func Pledge(promises string) {
9 | unix.PledgePromises(promises)
10 | }
11 |
--------------------------------------------------------------------------------
/browser-files/firefox-host.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.github.browserpass.native",
3 | "description": "Browserpass native component for the Firefox extension",
4 | "path": "%%replace%%",
5 | "type": "stdio",
6 | "allowed_extensions": ["browserpass@maximbaz.com"]
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.go]
12 | indent_style = tab
13 |
14 | [Makefile]
15 | indent_style = tab
16 |
--------------------------------------------------------------------------------
/debug/README.md:
--------------------------------------------------------------------------------
1 | To test whether the native host is working correctly, run:
2 | `/path/to/browserpass < /path/to/request-configure.hex.txt`
3 |
4 | Note that this is *not* a plain text file; it includes a binary header
5 | that may be damaged if you attempt to edit this file using a standard
6 | text editor.
7 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/browserpass/browserpass-native/v3
2 |
3 | go 1.24.0
4 |
5 | toolchain go1.24.6
6 |
7 | require (
8 | github.com/mattn/go-zglob v0.0.6
9 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
10 | github.com/sirupsen/logrus v1.9.3
11 | golang.org/x/sys v0.36.0
12 | )
13 |
--------------------------------------------------------------------------------
/persistentlog/unsupported.go:
--------------------------------------------------------------------------------
1 | // +build nacl,plan9
2 |
3 | package persistentlog
4 |
5 | import log "github.com/sirupsen/logrus"
6 |
7 | // AddPersistentLogHook configures persisting logs, not supported on these systems
8 | func AddPersistentLogHook() {
9 | log.Warn("Persistent logging is not implemented on this OS")
10 | }
11 |
--------------------------------------------------------------------------------
/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | import "fmt"
4 |
5 | const major = 3
6 | const minor = 1
7 | const patch = 2
8 |
9 | // Code version as integer
10 | const Code = major*1000000 + minor*1000 + patch
11 |
12 | // String version as string
13 | func String() string {
14 | return fmt.Sprintf("%d.%d.%d", major, minor, patch)
15 | }
16 |
--------------------------------------------------------------------------------
/browser-files/chromium-host.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.github.browserpass.native",
3 | "description": "Browserpass native component for the Chromium extension",
4 | "path": "%%replace%%",
5 | "type": "stdio",
6 | "allowed_origins": [
7 | "chrome-extension://naepdomgkenhinolocfifgehidddafch/",
8 | "chrome-extension://pjmbgaakjkbhpopmakjoedenlfdmcdgm/",
9 | "chrome-extension://klfoddkbhleoaabpmiigbmpbjfljimgb/"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/persistentlog/syslog.go:
--------------------------------------------------------------------------------
1 | // +build !windows,!nacl,!plan9
2 |
3 | package persistentlog
4 |
5 | import (
6 | "log/syslog"
7 |
8 | log "github.com/sirupsen/logrus"
9 | logSyslog "github.com/sirupsen/logrus/hooks/syslog"
10 | )
11 |
12 | // AddPersistentLogHook configures persisting logs in syslog
13 | func AddPersistentLogHook() {
14 | if hook, err := logSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "browserpass"); err != nil {
15 | log.Warn("Unable to connect to syslog, logs will NOT be persisted: ", err)
16 | } else {
17 | log.AddHook(hook)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2018-2019, Maxim Baz & Steve Gilberd
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/request/common.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 | "strings"
8 | )
9 |
10 | func normalizePasswordStorePath(storePath string) (string, error) {
11 | if storePath == "" {
12 | return "", errors.New("The store path cannot be empty")
13 | }
14 |
15 | if strings.HasPrefix(storePath, "~/") {
16 | storePath = filepath.Join("$HOME", storePath[2:])
17 | }
18 | storePath = os.ExpandEnv(storePath)
19 |
20 | directStorePath, err := filepath.EvalSymlinks(storePath)
21 | if err != nil {
22 | return "", err
23 | }
24 | storePath = directStorePath
25 |
26 | stat, err := os.Stat(storePath)
27 | if err != nil {
28 | return "", err
29 | }
30 | if !stat.IsDir() {
31 | return "", errors.New("The specified path exists, but is not a directory")
32 | }
33 | return storePath, nil
34 | }
35 |
--------------------------------------------------------------------------------
/persistentlog/windows.go:
--------------------------------------------------------------------------------
1 | // +build windows
2 |
3 | package persistentlog
4 |
5 | import (
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/rifflock/lfshook"
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | // AddPersistentLogHook configures persisting logs in a file
14 | func AddPersistentLogHook() {
15 | appDataPath := os.Getenv("LOCALAPPDATA")
16 | if appDataPath == "" {
17 | log.Warn("Unable to determine %%APPDATA%% folder location, logs will NOT be persisted")
18 | return
19 | }
20 | logFolderPath := filepath.Join(appDataPath, "browserpass")
21 | if err := os.MkdirAll(logFolderPath, os.ModePerm); err != nil {
22 | log.Warn("Unable to create browserpass folder in %%APPDATA%%, logs will NOT be persisted: ", err)
23 | return
24 | }
25 | logFilePath := filepath.Join(logFolderPath, "browserpass.log")
26 | log.Debug("Logs will being written to: ", logFilePath)
27 | log.AddHook(lfshook.NewHook(logFilePath, &log.TextFormatter{FullTimestamp: true}))
28 | }
29 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/browserpass/browserpass-native/v3/openbsd"
9 | "github.com/browserpass/browserpass-native/v3/persistentlog"
10 | "github.com/browserpass/browserpass-native/v3/request"
11 | "github.com/browserpass/browserpass-native/v3/version"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | func main() {
16 | var isVerbose bool
17 | var isVersion bool
18 | flag.BoolVar(&isVerbose, "v", false, "print verbose output")
19 | flag.BoolVar(&isVersion, "version", false, "print version and exit")
20 | flag.Parse()
21 |
22 | if isVersion {
23 | fmt.Println("Browserpass host app version:", version.String())
24 | os.Exit(0)
25 | }
26 |
27 | openbsd.Pledge("stdio rpath proc exec getpw unix tty")
28 |
29 | log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
30 | if isVerbose {
31 | log.SetLevel(log.DebugLevel)
32 | }
33 |
34 | persistentlog.AddPersistentLogHook()
35 |
36 | log.Debugf("Starting browserpass host app v%v", version.String())
37 | request.Process()
38 | }
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### General information
2 |
3 |
4 |
5 | - Operating system + version:
6 | - Browser + version:
7 | - Information about the host app:
8 | - How did you install it?
9 |
10 | - If installed an official release, put a version (`$ browserpass --version`):
11 | - If built from sources, put a commit id (`$ git describe --always`):
12 | - Information about the browser extension:
13 | - How did you install it?
14 |
15 | - Browserpass extension version as reported by your browser:
16 |
17 | ---
18 |
19 | If you are getting an error immediately after opening popup, have you followed the [Configure browsers](https://github.com/browserpass/browserpass-native#configure-browsers) documentation section?
20 |
21 | ---
22 |
23 | ### Exact steps to reproduce the problem
24 |
25 | 1.
26 |
27 | 2.
28 |
29 | 3.
30 |
31 | ### What should happen?
32 |
33 | ### What happened instead?
34 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
5 | github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
9 | github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
10 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
11 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
14 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
17 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
21 |
--------------------------------------------------------------------------------
/windows-setup.wxs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/errors/errors.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | // Code exit code
8 | type Code int
9 |
10 | // Error codes that are sent to the browser extension and used as exit codes in the app.
11 | // DO NOT MODIFY THE VALUES, always append new error codes to the bottom.
12 | const (
13 | CodeParseRequestLength Code = 10
14 | CodeParseRequest Code = 11
15 | CodeInvalidRequestAction Code = 12
16 | CodeInaccessiblePasswordStore Code = 13
17 | CodeInaccessibleDefaultPasswordStore Code = 14
18 | CodeUnknownDefaultPasswordStoreLocation Code = 15
19 | CodeUnreadablePasswordStoreDefaultSettings Code = 16
20 | CodeUnreadableDefaultPasswordStoreDefaultSettings Code = 17
21 | CodeUnableToListFilesInPasswordStore Code = 18
22 | CodeUnableToDetermineRelativeFilePathInPasswordStore Code = 19
23 | CodeInvalidPasswordStore Code = 20
24 | CodeInvalidGpgPath Code = 21
25 | CodeUnableToDetectGpgPath Code = 22
26 | CodeInvalidPasswordFileExtension Code = 23
27 | CodeUnableToDecryptPasswordFile Code = 24
28 | CodeUnableToListDirectoriesInPasswordStore Code = 25
29 | CodeUnableToDetermineRelativeDirectoryPathInPasswordStore Code = 26
30 | CodeEmptyContents Code = 27
31 | CodeUnableToDetermineGpgRecipients Code = 28
32 | CodeUnableToEncryptPasswordFile Code = 29
33 | CodeUnableToDeletePasswordFile Code = 30
34 | CodeUnableToDetermineIsDirectoryEmpty Code = 31
35 | CodeUnableToDeleteEmptyDirectory Code = 32
36 | )
37 |
38 | // Field extra field in the error response params
39 | type Field string
40 |
41 | // Extra fields that can be sent to the browser extension as part of an error response.
42 | // FieldMessage is always present, others are optional.
43 | const (
44 | FieldMessage Field = "message"
45 | FieldAction Field = "action"
46 | FieldError Field = "error"
47 | FieldStoreID Field = "storeId"
48 | FieldStoreName Field = "storeName"
49 | FieldStorePath Field = "storePath"
50 | FieldFile Field = "file"
51 | FieldDirectory Field = "directory"
52 | FieldGpgPath Field = "gpgPath"
53 | )
54 |
55 | // ExitWithCode exit with error code
56 | func ExitWithCode(code Code) {
57 | os.Exit(int(code))
58 | }
59 |
--------------------------------------------------------------------------------
/request/list.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "path/filepath"
5 | "sort"
6 | "strings"
7 |
8 | "github.com/browserpass/browserpass-native/v3/errors"
9 | "github.com/browserpass/browserpass-native/v3/response"
10 | "github.com/mattn/go-zglob"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | func listFiles(request *request) {
15 | responseData := response.MakeListResponse()
16 |
17 | for _, store := range request.Settings.Stores {
18 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
19 | if err != nil {
20 | log.Errorf(
21 | "The password store '%+v' is not accessible at its location: %+v",
22 | store, err,
23 | )
24 | response.SendErrorAndExit(
25 | errors.CodeInaccessiblePasswordStore,
26 | &map[errors.Field]string{
27 | errors.FieldMessage: "The password store is not accessible",
28 | errors.FieldAction: "list",
29 | errors.FieldError: err.Error(),
30 | errors.FieldStoreID: store.ID,
31 | errors.FieldStoreName: store.Name,
32 | errors.FieldStorePath: store.Path,
33 | },
34 | )
35 | }
36 |
37 | store.Path = normalizedStorePath
38 |
39 | files, err := zglob.GlobFollowSymlinks(filepath.Join(store.Path, "/**/*.gpg"))
40 | if err != nil {
41 | log.Errorf(
42 | "Unable to list the files in the password store '%+v' at its location: %+v",
43 | store, err,
44 | )
45 | response.SendErrorAndExit(
46 | errors.CodeUnableToListFilesInPasswordStore,
47 | &map[errors.Field]string{
48 | errors.FieldMessage: "Unable to list the files in the password store",
49 | errors.FieldAction: "list",
50 | errors.FieldError: err.Error(),
51 | errors.FieldStoreID: store.ID,
52 | errors.FieldStoreName: store.Name,
53 | errors.FieldStorePath: store.Path,
54 | },
55 | )
56 | }
57 |
58 | for i, file := range files {
59 | relativePath, err := filepath.Rel(store.Path, file)
60 | if err != nil {
61 | log.Errorf(
62 | "Unable to determine the relative path for a file '%v' in the password store '%+v': %+v",
63 | file, store, err,
64 | )
65 | response.SendErrorAndExit(
66 | errors.CodeUnableToDetermineRelativeFilePathInPasswordStore,
67 | &map[errors.Field]string{
68 | errors.FieldMessage: "Unable to determine the relative path for a file in the password store",
69 | errors.FieldAction: "list",
70 | errors.FieldError: err.Error(),
71 | errors.FieldFile: file,
72 | errors.FieldStoreID: store.ID,
73 | errors.FieldStoreName: store.Name,
74 | errors.FieldStorePath: store.Path,
75 | },
76 | )
77 | }
78 | files[i] = strings.Replace(relativePath, "\\", "/", -1) // normalize Windows paths
79 | }
80 |
81 | sort.Strings(files)
82 | responseData.Files[store.ID] = files
83 | }
84 |
85 | response.SendOk(responseData)
86 | }
87 |
--------------------------------------------------------------------------------
/request/process_test.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io"
7 | "reflect"
8 | "testing"
9 | )
10 |
11 | func Test_ParseRequestLength_ConsidersFirstFourBytes(t *testing.T) {
12 | // Arrange
13 | expected := uint32(201334791) // 0x0c002007
14 |
15 | // The first 4 bytes represent the value of `expected` in Little Endian format,
16 | // the rest should be completely ignored during the parsing.
17 | input := bytes.NewReader([]byte{7, 32, 0, 12, 13, 13, 13})
18 |
19 | // Act
20 | actual, err := parseRequestLength(input)
21 |
22 | // Assert
23 | if err != nil {
24 | t.Fatalf("Error parsing request length: %v", err)
25 | }
26 |
27 | if expected != actual {
28 | t.Fatalf("The actual length '%v' does not match the expected value of '%v'", actual, expected)
29 | }
30 | }
31 |
32 | func Test_ParseRequestLength_ConnectionAborted(t *testing.T) {
33 | // Arrange
34 | expectedErr := io.ErrUnexpectedEOF
35 | input := bytes.NewReader([]byte{7})
36 |
37 | // Act
38 | _, err := parseRequestLength(input)
39 |
40 | // Assert
41 | if expectedErr != err {
42 | t.Fatalf("The expected error is '%v', but got '%v'", expectedErr, err)
43 | }
44 | }
45 |
46 | func Test_ParseRequest_CanParse(t *testing.T) {
47 | // Arrange
48 | expected := &request{
49 | Action: "list",
50 | Settings: settings{
51 | Stores: map[string]store{
52 | "id1": store{
53 | ID: "id1",
54 | Name: "default",
55 | Path: "~/.password-store",
56 | },
57 | },
58 | },
59 | }
60 |
61 | jsonBytes, err := json.Marshal(expected)
62 | if err != nil {
63 | t.Fatal("Unable to marshal the expected object to initialize the test")
64 | }
65 |
66 | inputLength := uint32(len(jsonBytes))
67 | input := bytes.NewReader(jsonBytes)
68 |
69 | // Act
70 | actual, err := parseRequest(inputLength, input)
71 |
72 | // Assert
73 | if err != nil {
74 | t.Fatalf("Error parsing request: %v", err)
75 | }
76 |
77 | if !reflect.DeepEqual(expected, actual) {
78 | t.Fatalf("The request was parsed incorrectly.\nExpected: %+v\nActual: %+v", expected, actual)
79 | }
80 | }
81 |
82 | func Test_ParseRequest_WrongLength(t *testing.T) {
83 | // Arrange
84 | expectedErr := io.ErrUnexpectedEOF
85 |
86 | jsonBytes, err := json.Marshal(&request{Action: "list"})
87 | if err != nil {
88 | t.Fatal("Unable to marshal the expected object to initialize the test")
89 | }
90 |
91 | wrongInputLength := uint32(len(jsonBytes)) - 1
92 | input := bytes.NewReader(jsonBytes)
93 |
94 | // Act
95 | _, err = parseRequest(wrongInputLength, input)
96 |
97 | // Assert
98 | if expectedErr != err {
99 | t.Fatalf("The expected error is '%v', but got '%v'", expectedErr, err)
100 | }
101 | }
102 |
103 | func Test_ParseRequest_InvalidJson(t *testing.T) {
104 | // Arrange
105 | jsonBytes := []byte("not_a_json")
106 | inputLength := uint32(len(jsonBytes))
107 | input := bytes.NewReader(jsonBytes)
108 |
109 | // Act
110 | _, err := parseRequest(inputLength, input)
111 |
112 | // Assert
113 | if err == nil {
114 | t.Fatalf("Expected a parsing error, but didn't get it")
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/helpers/helpers.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "os/exec"
10 | "path/filepath"
11 | "strings"
12 | )
13 |
14 | func DetectGpgBinary() (string, error) {
15 | // Look in $PATH first, then check common locations - the first successful result wins
16 | gpgBinaryPriorityList := []string{
17 | "gpg2", "gpg",
18 | "/bin/gpg2", "/usr/bin/gpg2", "/usr/local/bin/gpg2",
19 | "/bin/gpg", "/usr/bin/gpg", "/usr/local/bin/gpg",
20 | }
21 |
22 | for _, binary := range gpgBinaryPriorityList {
23 | err := ValidateGpgBinary(binary)
24 | if err == nil {
25 | return binary, nil
26 | }
27 | }
28 | return "", fmt.Errorf("Unable to detect the location of the gpg binary to use")
29 | }
30 |
31 | func ValidateGpgBinary(gpgPath string) error {
32 | return exec.Command(gpgPath, "--version").Run()
33 | }
34 |
35 | func GpgDecryptFile(filePath string, gpgPath string) (string, error) {
36 | passwordFile, err := os.Open(filePath)
37 | if err != nil {
38 | return "", err
39 | }
40 |
41 | var stdout, stderr bytes.Buffer
42 | gpgOptions := []string{"--decrypt", "--yes", "--quiet", "--batch", "-"}
43 |
44 | cmd := exec.Command(gpgPath, gpgOptions...)
45 | cmd.Stdin = passwordFile
46 | cmd.Stdout = &stdout
47 | cmd.Stderr = &stderr
48 |
49 | if err := cmd.Run(); err != nil {
50 | return "", fmt.Errorf("Error: %s, Stderr: %s", err.Error(), stderr.String())
51 | }
52 |
53 | return stdout.String(), nil
54 | }
55 |
56 | func GpgEncryptFile(filePath string, contents string, recipients []string, gpgPath string) error {
57 | err := os.MkdirAll(filepath.Dir(filePath), 0755)
58 | if err != nil {
59 | return fmt.Errorf("Unable to create directory structure: %s", err.Error())
60 | }
61 |
62 | var stdout, stderr bytes.Buffer
63 | gpgOptions := []string{"--encrypt", "--yes", "--quiet", "--batch", "--output", filePath}
64 | for _, recipient := range recipients {
65 | gpgOptions = append(gpgOptions, "--recipient", recipient)
66 | }
67 |
68 | cmd := exec.Command(gpgPath, gpgOptions...)
69 | cmd.Stdin = strings.NewReader(contents)
70 | cmd.Stdout = &stdout
71 | cmd.Stderr = &stderr
72 |
73 | if err = cmd.Run(); err != nil {
74 | return fmt.Errorf("Error: %s, Stderr: %s", err.Error(), stderr.String())
75 | }
76 |
77 | return nil
78 | }
79 |
80 | func DetectGpgRecipients(filePath string) ([]string, error) {
81 | dir := filepath.Dir(filePath)
82 | for {
83 | file, err := ioutil.ReadFile(filepath.Join(dir, ".gpg-id"))
84 | if err == nil {
85 | return strings.Split(strings.ReplaceAll(strings.TrimSpace(string(file)), "\r\n", "\n"), "\n"), nil
86 | }
87 |
88 | if !os.IsNotExist(err) {
89 | return nil, fmt.Errorf("Unable to open `.gpg-id` file: %s", err.Error())
90 | }
91 |
92 | parentDir := filepath.Dir(dir)
93 | if parentDir == dir {
94 | return nil, fmt.Errorf("Unable to find '.gpg-id' file")
95 | }
96 |
97 | dir = parentDir
98 | }
99 | }
100 |
101 | func IsDirectoryEmpty(dirPath string) (bool, error) {
102 | f, err := os.Open(dirPath)
103 | if err != nil {
104 | return false, err
105 | }
106 | defer f.Close()
107 |
108 | _, err = f.Readdirnames(1)
109 | if err == io.EOF {
110 | return true, nil
111 | }
112 |
113 | return false, err
114 | }
115 |
--------------------------------------------------------------------------------
/request/process.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/binary"
5 | "encoding/json"
6 | "io"
7 | "os"
8 |
9 | "github.com/browserpass/browserpass-native/v3/errors"
10 | "github.com/browserpass/browserpass-native/v3/response"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | type StoreSettings struct {
15 | GpgPath string `json:"gpgPath"`
16 | }
17 |
18 | type store struct {
19 | ID string `json:"id"`
20 | Name string `json:"name"`
21 | Path string `json:"path"`
22 | Settings StoreSettings `json:"settings"`
23 | }
24 |
25 | type settings struct {
26 | GpgPath string `json:"gpgPath"`
27 | Stores map[string]store `json:"stores"`
28 | }
29 |
30 | type request struct {
31 | Action string `json:"action"`
32 | Settings settings `json:"settings"`
33 | File string `json:"file"`
34 | Contents string `json:"contents"`
35 | StoreID string `json:"storeId"`
36 | EchoResponse interface{} `json:"echoResponse"`
37 | }
38 |
39 | // Process handles browser request
40 | func Process() {
41 | requestLength, err := parseRequestLength(os.Stdin)
42 | if err != nil {
43 | log.Error("Unable to parse the length of the browser request: ", err)
44 | response.SendErrorAndExit(
45 | errors.CodeParseRequestLength,
46 | &map[errors.Field]string{
47 | errors.FieldMessage: "Unable to parse the length of the browser request",
48 | errors.FieldError: err.Error(),
49 | },
50 | )
51 | }
52 |
53 | request, err := parseRequest(requestLength, os.Stdin)
54 | if err != nil {
55 | log.Error("Unable to parse the browser request: ", err)
56 | response.SendErrorAndExit(
57 | errors.CodeParseRequest,
58 | &map[errors.Field]string{
59 | errors.FieldMessage: "Unable to parse the browser request",
60 | errors.FieldError: err.Error(),
61 | },
62 | )
63 | }
64 |
65 | switch request.Action {
66 | case "configure":
67 | configure(request)
68 | case "list":
69 | listFiles(request)
70 | case "tree":
71 | listDirectories(request)
72 | case "fetch":
73 | fetchDecryptedContents(request)
74 | case "save":
75 | saveEncryptedContents(request)
76 | case "delete":
77 | deleteFile(request)
78 | case "echo":
79 | response.SendRaw(request.EchoResponse)
80 | default:
81 | log.Errorf("Received a browser request with an unknown action: %+v", request)
82 | response.SendErrorAndExit(
83 | errors.CodeInvalidRequestAction,
84 | &map[errors.Field]string{
85 | errors.FieldMessage: "Invalid request action",
86 | errors.FieldAction: request.Action,
87 | },
88 | )
89 | }
90 | }
91 |
92 | // Request length is the first 4 bytes in LittleEndian encoding
93 | func parseRequestLength(input io.Reader) (uint32, error) {
94 | var length uint32
95 | if err := binary.Read(input, binary.LittleEndian, &length); err != nil {
96 | return 0, err
97 | }
98 | return length, nil
99 | }
100 |
101 | // Request is a json with a predefined structure
102 | func parseRequest(messageLength uint32, input io.Reader) (*request, error) {
103 | var parsed request
104 | reader := &io.LimitedReader{R: input, N: int64(messageLength)}
105 | if err := json.NewDecoder(reader).Decode(&parsed); err != nil {
106 | return nil, err
107 | }
108 | return &parsed, nil
109 | }
110 |
--------------------------------------------------------------------------------
/request/tree.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "sort"
7 | "strings"
8 | "sync"
9 |
10 | "github.com/browserpass/browserpass-native/v3/errors"
11 | "github.com/browserpass/browserpass-native/v3/response"
12 | "github.com/mattn/go-zglob/fastwalk"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | func listDirectories(request *request) {
17 | responseData := response.MakeTreeResponse()
18 |
19 | for _, store := range request.Settings.Stores {
20 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
21 | if err != nil {
22 | log.Errorf(
23 | "The password store '%+v' is not accessible at its location: %+v",
24 | store, err,
25 | )
26 | response.SendErrorAndExit(
27 | errors.CodeInaccessiblePasswordStore,
28 | &map[errors.Field]string{
29 | errors.FieldMessage: "The password store is not accessible",
30 | errors.FieldAction: "tree",
31 | errors.FieldError: err.Error(),
32 | errors.FieldStoreID: store.ID,
33 | errors.FieldStoreName: store.Name,
34 | errors.FieldStorePath: store.Path,
35 | },
36 | )
37 | }
38 |
39 | store.Path = normalizedStorePath
40 |
41 | var mu sync.Mutex
42 | directories := []string{}
43 | err = fastwalk.FastWalk(store.Path, func(path string, typ os.FileMode) error {
44 | if typ == os.ModeSymlink {
45 | followedPath, err := filepath.EvalSymlinks(path)
46 | if err == nil {
47 | fi, err := os.Lstat(followedPath)
48 | if err == nil && fi.IsDir() {
49 | return fastwalk.TraverseLink
50 | }
51 | }
52 | }
53 |
54 | if typ.IsDir() && path != store.Path {
55 | if filepath.Base(path) == ".git" {
56 | return filepath.SkipDir
57 | }
58 | mu.Lock()
59 | directories = append(directories, path)
60 | mu.Unlock()
61 | }
62 |
63 | return nil
64 | })
65 |
66 | if err != nil {
67 | log.Errorf(
68 | "Unable to list the directory tree in the password store '%+v' at its location: %+v",
69 | store, err,
70 | )
71 | response.SendErrorAndExit(
72 | errors.CodeUnableToListDirectoriesInPasswordStore,
73 | &map[errors.Field]string{
74 | errors.FieldMessage: "Unable to list the directory tree in the password store",
75 | errors.FieldAction: "tree",
76 | errors.FieldError: err.Error(),
77 | errors.FieldStoreID: store.ID,
78 | errors.FieldStoreName: store.Name,
79 | errors.FieldStorePath: store.Path,
80 | },
81 | )
82 | }
83 |
84 | for i, directory := range directories {
85 | relativePath, err := filepath.Rel(store.Path, directory)
86 | if err != nil {
87 | log.Errorf(
88 | "Unable to determine the relative path for a file '%v' in the password store '%+v': %+v",
89 | directory, store, err,
90 | )
91 | response.SendErrorAndExit(
92 | errors.CodeUnableToDetermineRelativeDirectoryPathInPasswordStore,
93 | &map[errors.Field]string{
94 | errors.FieldMessage: "Unable to determine the relative path for a directory in the password store",
95 | errors.FieldAction: "tree",
96 | errors.FieldError: err.Error(),
97 | errors.FieldDirectory: directory,
98 | errors.FieldStoreID: store.ID,
99 | errors.FieldStoreName: store.Name,
100 | errors.FieldStorePath: store.Path,
101 | },
102 | )
103 | }
104 | directories[i] = strings.Replace(relativePath, "\\", "/", -1) // normalize Windows paths
105 | }
106 |
107 | sort.Strings(directories)
108 | responseData.Directories[store.ID] = directories
109 | }
110 |
111 | response.SendOk(responseData)
112 | }
113 |
--------------------------------------------------------------------------------
/response/response.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "encoding/json"
7 | "os"
8 |
9 | "github.com/browserpass/browserpass-native/v3/errors"
10 | "github.com/browserpass/browserpass-native/v3/version"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | type okResponse struct {
15 | Status string `json:"status"`
16 | Version int `json:"version"`
17 | Data interface{} `json:"data"`
18 | }
19 |
20 | type errorResponse struct {
21 | Status string `json:"status"`
22 | Code errors.Code `json:"code"`
23 | Version int `json:"version"`
24 | Params interface{} `json:"params"`
25 | }
26 |
27 | // ConfigureResponse a response format for the "configure" request
28 | type ConfigureResponse struct {
29 | DefaultStore struct {
30 | Path string `json:"path"`
31 | Settings string `json:"settings"`
32 | } `json:"defaultStore"`
33 | StoreSettings map[string]string `json:"storeSettings"`
34 | }
35 |
36 | // MakeConfigureResponse initializes an empty configure response
37 | func MakeConfigureResponse() *ConfigureResponse {
38 | return &ConfigureResponse{
39 | StoreSettings: make(map[string]string),
40 | }
41 | }
42 |
43 | // ListResponse a response format for the "list" request
44 | type ListResponse struct {
45 | Files map[string][]string `json:"files"`
46 | }
47 |
48 | // MakeListResponse initializes an empty list response
49 | func MakeListResponse() *ListResponse {
50 | return &ListResponse{
51 | Files: make(map[string][]string),
52 | }
53 | }
54 |
55 | // TreeResponse a response format for the "tree" request
56 | type TreeResponse struct {
57 | Directories map[string][]string `json:"directories"`
58 | }
59 |
60 | // MakeTreeResponse initializes an empty tree response
61 | func MakeTreeResponse() *TreeResponse {
62 | return &TreeResponse{
63 | Directories: make(map[string][]string),
64 | }
65 | }
66 |
67 | // FetchResponse a response format for the "fetch" request
68 | type FetchResponse struct {
69 | Contents string `json:"contents"`
70 | }
71 |
72 | // MakeFetchResponse initializes an empty fetch response
73 | func MakeFetchResponse() *FetchResponse {
74 | return &FetchResponse{}
75 | }
76 |
77 | // SaveResponse a response format for the "save" request
78 | type SaveResponse struct {
79 | }
80 |
81 | // MakeSaveResponse initializes an empty save response
82 | func MakeSaveResponse() *SaveResponse {
83 | return &SaveResponse{}
84 | }
85 |
86 | // DeleteResponse a response format for the "delete" request
87 | type DeleteResponse struct {
88 | }
89 |
90 | // MakeDeleteResponse initializes an empty delete response
91 | func MakeDeleteResponse() *DeleteResponse {
92 | return &DeleteResponse{}
93 | }
94 |
95 | // SendOk sends a success response to the browser extension in the predefined json format
96 | func SendOk(data interface{}) {
97 | SendRaw(&okResponse{
98 | Status: "ok",
99 | Version: version.Code,
100 | Data: data,
101 | })
102 | }
103 |
104 | // SendErrorAndExit sends an error response to the browser extension in the predefined json format and exits with the specified exit code
105 | func SendErrorAndExit(errorCode errors.Code, params *map[errors.Field]string) {
106 | SendRaw(&errorResponse{
107 | Status: "error",
108 | Code: errorCode,
109 | Version: version.Code,
110 | Params: params,
111 | })
112 |
113 | errors.ExitWithCode(errorCode)
114 | }
115 |
116 | // SendRaw sends a raw data to the browser extension
117 | func SendRaw(response interface{}) {
118 | var bytesBuffer bytes.Buffer
119 | if err := json.NewEncoder(&bytesBuffer).Encode(response); err != nil {
120 | log.Fatal("Unable to encode response for sending: ", err)
121 | }
122 |
123 | if err := binary.Write(os.Stdout, binary.LittleEndian, uint32(bytesBuffer.Len())); err != nil {
124 | log.Fatal("Unable to send the length of the response: ", err)
125 | }
126 | if _, err := bytesBuffer.WriteTo(os.Stdout); err != nil {
127 | log.Fatal("Unable to send the response: ", err)
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/request/fetch.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "path/filepath"
5 | "strings"
6 |
7 | "github.com/browserpass/browserpass-native/v3/errors"
8 | "github.com/browserpass/browserpass-native/v3/helpers"
9 | "github.com/browserpass/browserpass-native/v3/response"
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | func fetchDecryptedContents(request *request) {
14 | responseData := response.MakeFetchResponse()
15 |
16 | if !strings.HasSuffix(request.File, ".gpg") {
17 | log.Errorf("The requested password file '%v' does not have the expected '.gpg' extension", request.File)
18 | response.SendErrorAndExit(
19 | errors.CodeInvalidPasswordFileExtension,
20 | &map[errors.Field]string{
21 | errors.FieldMessage: "The requested password file does not have the expected '.gpg' extension",
22 | errors.FieldAction: "fetch",
23 | errors.FieldFile: request.File,
24 | },
25 | )
26 | }
27 |
28 | store, ok := request.Settings.Stores[request.StoreID]
29 | if !ok {
30 | log.Errorf(
31 | "The password store with ID '%v' is not present in the list of stores '%+v'",
32 | request.StoreID, request.Settings.Stores,
33 | )
34 | response.SendErrorAndExit(
35 | errors.CodeInvalidPasswordStore,
36 | &map[errors.Field]string{
37 | errors.FieldMessage: "The password store is not present in the list of stores",
38 | errors.FieldAction: "fetch",
39 | errors.FieldStoreID: request.StoreID,
40 | },
41 | )
42 | }
43 |
44 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
45 | if err != nil {
46 | log.Errorf(
47 | "The password store '%+v' is not accessible at its location: %+v",
48 | store, err,
49 | )
50 | response.SendErrorAndExit(
51 | errors.CodeInaccessiblePasswordStore,
52 | &map[errors.Field]string{
53 | errors.FieldMessage: "The password store is not accessible",
54 | errors.FieldAction: "fetch",
55 | errors.FieldError: err.Error(),
56 | errors.FieldStoreID: store.ID,
57 | errors.FieldStoreName: store.Name,
58 | errors.FieldStorePath: store.Path,
59 | },
60 | )
61 | }
62 | store.Path = normalizedStorePath
63 |
64 | var gpgPath string
65 | if request.Settings.GpgPath != "" || store.Settings.GpgPath != "" {
66 | if request.Settings.GpgPath != "" {
67 | gpgPath = request.Settings.GpgPath
68 | } else {
69 | gpgPath = store.Settings.GpgPath
70 | }
71 | err = helpers.ValidateGpgBinary(gpgPath)
72 | if err != nil {
73 | log.Errorf(
74 | "The provided gpg binary path '%v' is invalid: %+v",
75 | gpgPath, err,
76 | )
77 | response.SendErrorAndExit(
78 | errors.CodeInvalidGpgPath,
79 | &map[errors.Field]string{
80 | errors.FieldMessage: "The provided gpg binary path is invalid",
81 | errors.FieldAction: "fetch",
82 | errors.FieldError: err.Error(),
83 | errors.FieldGpgPath: gpgPath,
84 | },
85 | )
86 | }
87 | } else {
88 | gpgPath, err = helpers.DetectGpgBinary()
89 | if err != nil {
90 | log.Error("Unable to detect the location of the gpg binary: ", err)
91 | response.SendErrorAndExit(
92 | errors.CodeUnableToDetectGpgPath,
93 | &map[errors.Field]string{
94 | errors.FieldMessage: "Unable to detect the location of the gpg binary",
95 | errors.FieldAction: "fetch",
96 | errors.FieldError: err.Error(),
97 | },
98 | )
99 | }
100 | }
101 |
102 | responseData.Contents, err = helpers.GpgDecryptFile(filepath.Join(store.Path, request.File), gpgPath)
103 | if err != nil {
104 | log.Errorf(
105 | "Unable to decrypt the password file '%v' in the password store '%+v': %+v",
106 | request.File, store, err,
107 | )
108 | response.SendErrorAndExit(
109 | errors.CodeUnableToDecryptPasswordFile,
110 | &map[errors.Field]string{
111 | errors.FieldMessage: "Unable to decrypt the password file",
112 | errors.FieldAction: "fetch",
113 | errors.FieldError: err.Error(),
114 | errors.FieldFile: request.File,
115 | errors.FieldStoreID: store.ID,
116 | errors.FieldStoreName: store.Name,
117 | errors.FieldStorePath: store.Path,
118 | },
119 | )
120 | }
121 |
122 | response.SendOk(responseData)
123 | }
124 |
--------------------------------------------------------------------------------
/request/delete.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 |
8 | "github.com/browserpass/browserpass-native/v3/errors"
9 | "github.com/browserpass/browserpass-native/v3/helpers"
10 | "github.com/browserpass/browserpass-native/v3/response"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | func deleteFile(request *request) {
15 | responseData := response.MakeDeleteResponse()
16 |
17 | if !strings.HasSuffix(request.File, ".gpg") {
18 | log.Errorf("The requested password file '%v' does not have the expected '.gpg' extension", request.File)
19 | response.SendErrorAndExit(
20 | errors.CodeInvalidPasswordFileExtension,
21 | &map[errors.Field]string{
22 | errors.FieldMessage: "The requested password file does not have the expected '.gpg' extension",
23 | errors.FieldAction: "delete",
24 | errors.FieldFile: request.File,
25 | },
26 | )
27 | }
28 |
29 | store, ok := request.Settings.Stores[request.StoreID]
30 | if !ok {
31 | log.Errorf(
32 | "The password store with ID '%v' is not present in the list of stores '%+v'",
33 | request.StoreID, request.Settings.Stores,
34 | )
35 | response.SendErrorAndExit(
36 | errors.CodeInvalidPasswordStore,
37 | &map[errors.Field]string{
38 | errors.FieldMessage: "The password store is not present in the list of stores",
39 | errors.FieldAction: "delete",
40 | errors.FieldStoreID: request.StoreID,
41 | },
42 | )
43 | }
44 |
45 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
46 | if err != nil {
47 | log.Errorf(
48 | "The password store '%+v' is not accessible at its location: %+v",
49 | store, err,
50 | )
51 | response.SendErrorAndExit(
52 | errors.CodeInaccessiblePasswordStore,
53 | &map[errors.Field]string{
54 | errors.FieldMessage: "The password store is not accessible",
55 | errors.FieldAction: "delete",
56 | errors.FieldError: err.Error(),
57 | errors.FieldStoreID: store.ID,
58 | errors.FieldStoreName: store.Name,
59 | errors.FieldStorePath: store.Path,
60 | },
61 | )
62 | }
63 | store.Path = normalizedStorePath
64 |
65 | filePath := filepath.Join(store.Path, request.File)
66 |
67 | err = os.Remove(filePath)
68 | if err != nil {
69 | log.Error("Unable to delete the password file: ", err)
70 | response.SendErrorAndExit(
71 | errors.CodeUnableToDeletePasswordFile,
72 | &map[errors.Field]string{
73 | errors.FieldMessage: "Unable to delete the password file",
74 | errors.FieldAction: "delete",
75 | errors.FieldError: err.Error(),
76 | errors.FieldFile: request.File,
77 | errors.FieldStoreID: store.ID,
78 | errors.FieldStoreName: store.Name,
79 | errors.FieldStorePath: store.Path,
80 | },
81 | )
82 | }
83 |
84 | parentDir := filepath.Dir(filePath)
85 | for {
86 | if parentDir == store.Path {
87 | break
88 | }
89 |
90 | isEmpty, err := helpers.IsDirectoryEmpty(parentDir)
91 | if err != nil {
92 | log.Error("Unable to determine if directory is empty and can be deleted: ", err)
93 | response.SendErrorAndExit(
94 | errors.CodeUnableToDetermineIsDirectoryEmpty,
95 | &map[errors.Field]string{
96 | errors.FieldMessage: "Unable to determine if directory is empty and can be deleted",
97 | errors.FieldAction: "delete",
98 | errors.FieldError: err.Error(),
99 | errors.FieldDirectory: parentDir,
100 | errors.FieldStoreID: store.ID,
101 | errors.FieldStoreName: store.Name,
102 | errors.FieldStorePath: store.Path,
103 | },
104 | )
105 | }
106 |
107 | if !isEmpty {
108 | break
109 | }
110 |
111 | err = os.Remove(parentDir)
112 | if err != nil {
113 | log.Error("Unable to delete the empty directory: ", err)
114 | response.SendErrorAndExit(
115 | errors.CodeUnableToDeleteEmptyDirectory,
116 | &map[errors.Field]string{
117 | errors.FieldMessage: "Unable to delete the empty directory",
118 | errors.FieldAction: "delete",
119 | errors.FieldError: err.Error(),
120 | errors.FieldDirectory: parentDir,
121 | errors.FieldStoreID: store.ID,
122 | errors.FieldStoreName: store.Name,
123 | errors.FieldStorePath: store.Path,
124 | },
125 | )
126 | }
127 |
128 | parentDir = filepath.Dir(parentDir)
129 | }
130 |
131 | response.SendOk(responseData)
132 | }
133 |
--------------------------------------------------------------------------------
/request/save.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "path/filepath"
5 | "strings"
6 |
7 | "github.com/browserpass/browserpass-native/v3/errors"
8 | "github.com/browserpass/browserpass-native/v3/helpers"
9 | "github.com/browserpass/browserpass-native/v3/response"
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | func saveEncryptedContents(request *request) {
14 | responseData := response.MakeSaveResponse()
15 |
16 | if !strings.HasSuffix(request.File, ".gpg") {
17 | log.Errorf("The requested password file '%v' does not have the expected '.gpg' extension", request.File)
18 | response.SendErrorAndExit(
19 | errors.CodeInvalidPasswordFileExtension,
20 | &map[errors.Field]string{
21 | errors.FieldMessage: "The requested password file does not have the expected '.gpg' extension",
22 | errors.FieldAction: "save",
23 | errors.FieldFile: request.File,
24 | },
25 | )
26 | }
27 |
28 | if request.Contents == "" {
29 | log.Errorf("The entry contents is missing")
30 | response.SendErrorAndExit(
31 | errors.CodeEmptyContents,
32 | &map[errors.Field]string{
33 | errors.FieldMessage: "The entry contents is missing",
34 | errors.FieldAction: "save",
35 | },
36 | )
37 | }
38 |
39 | store, ok := request.Settings.Stores[request.StoreID]
40 | if !ok {
41 | log.Errorf(
42 | "The password store with ID '%v' is not present in the list of stores '%+v'",
43 | request.StoreID, request.Settings.Stores,
44 | )
45 | response.SendErrorAndExit(
46 | errors.CodeInvalidPasswordStore,
47 | &map[errors.Field]string{
48 | errors.FieldMessage: "The password store is not present in the list of stores",
49 | errors.FieldAction: "save",
50 | errors.FieldStoreID: request.StoreID,
51 | },
52 | )
53 | }
54 |
55 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
56 | if err != nil {
57 | log.Errorf(
58 | "The password store '%+v' is not accessible at its location: %+v",
59 | store, err,
60 | )
61 | response.SendErrorAndExit(
62 | errors.CodeInaccessiblePasswordStore,
63 | &map[errors.Field]string{
64 | errors.FieldMessage: "The password store is not accessible",
65 | errors.FieldAction: "save",
66 | errors.FieldError: err.Error(),
67 | errors.FieldStoreID: store.ID,
68 | errors.FieldStoreName: store.Name,
69 | errors.FieldStorePath: store.Path,
70 | },
71 | )
72 | }
73 | store.Path = normalizedStorePath
74 |
75 | var gpgPath string
76 | if request.Settings.GpgPath != "" || store.Settings.GpgPath != "" {
77 | if request.Settings.GpgPath != "" {
78 | gpgPath = request.Settings.GpgPath
79 | } else {
80 | gpgPath = store.Settings.GpgPath
81 | }
82 | err = helpers.ValidateGpgBinary(gpgPath)
83 | if err != nil {
84 | log.Errorf(
85 | "The provided gpg binary path '%v' is invalid: %+v",
86 | gpgPath, err,
87 | )
88 | response.SendErrorAndExit(
89 | errors.CodeInvalidGpgPath,
90 | &map[errors.Field]string{
91 | errors.FieldMessage: "The provided gpg binary path is invalid",
92 | errors.FieldAction: "save",
93 | errors.FieldError: err.Error(),
94 | errors.FieldGpgPath: gpgPath,
95 | },
96 | )
97 | }
98 | } else {
99 | gpgPath, err = helpers.DetectGpgBinary()
100 | if err != nil {
101 | log.Error("Unable to detect the location of the gpg binary: ", err)
102 | response.SendErrorAndExit(
103 | errors.CodeUnableToDetectGpgPath,
104 | &map[errors.Field]string{
105 | errors.FieldMessage: "Unable to detect the location of the gpg binary",
106 | errors.FieldAction: "save",
107 | errors.FieldError: err.Error(),
108 | },
109 | )
110 | }
111 | }
112 |
113 | filePath := filepath.Join(store.Path, request.File)
114 |
115 | recipients, err := helpers.DetectGpgRecipients(filePath)
116 | if err != nil {
117 | log.Error("Unable to determine recipients for the gpg encryption: ", err)
118 | response.SendErrorAndExit(
119 | errors.CodeUnableToDetermineGpgRecipients,
120 | &map[errors.Field]string{
121 | errors.FieldMessage: "Unable to determine recipients for the gpg encryption",
122 | errors.FieldAction: "save",
123 | errors.FieldError: err.Error(),
124 | errors.FieldFile: request.File,
125 | errors.FieldStoreID: store.ID,
126 | errors.FieldStoreName: store.Name,
127 | errors.FieldStorePath: store.Path,
128 | },
129 | )
130 | }
131 |
132 | err = helpers.GpgEncryptFile(filePath, request.Contents, recipients, gpgPath)
133 | if err != nil {
134 | log.Errorf(
135 | "Unable to encrypt the password file '%v' in the password store '%+v': %+v",
136 | request.File, store, err,
137 | )
138 | response.SendErrorAndExit(
139 | errors.CodeUnableToEncryptPasswordFile,
140 | &map[errors.Field]string{
141 | errors.FieldMessage: "Unable to encrypt the password file",
142 | errors.FieldAction: "save",
143 | errors.FieldError: err.Error(),
144 | errors.FieldFile: request.File,
145 | errors.FieldStoreID: store.ID,
146 | errors.FieldStoreName: store.Name,
147 | errors.FieldStorePath: store.Path,
148 | },
149 | )
150 | }
151 |
152 | response.SendOk(responseData)
153 | }
154 |
--------------------------------------------------------------------------------
/request/configure.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/browserpass/browserpass-native/v3/errors"
10 | "github.com/browserpass/browserpass-native/v3/helpers"
11 | "github.com/browserpass/browserpass-native/v3/response"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | func configure(request *request) {
16 | responseData := response.MakeConfigureResponse()
17 |
18 | // User configured gpgPath in the browser, check if it is a valid binary to use
19 | if request.Settings.GpgPath != "" {
20 | err := helpers.ValidateGpgBinary(request.Settings.GpgPath)
21 | if err != nil {
22 | log.Errorf(
23 | "The provided gpg binary path '%v' is invalid: %+v",
24 | request.Settings.GpgPath, err,
25 | )
26 | response.SendErrorAndExit(
27 | errors.CodeInvalidGpgPath,
28 | &map[errors.Field]string{
29 | errors.FieldMessage: "The provided gpg binary path is invalid",
30 | errors.FieldAction: "configure",
31 | errors.FieldError: err.Error(),
32 | errors.FieldGpgPath: request.Settings.GpgPath,
33 | },
34 | )
35 | }
36 | }
37 |
38 | // Check that each and every store in the settings exists and is accessible.
39 | // Then read the default configuration for these stores (if available).
40 | for _, store := range request.Settings.Stores {
41 | normalizedStorePath, err := normalizePasswordStorePath(store.Path)
42 | if err != nil {
43 | log.Errorf(
44 | "The password store '%+v' is not accessible at its location: %+v",
45 | store, err,
46 | )
47 | response.SendErrorAndExit(
48 | errors.CodeInaccessiblePasswordStore,
49 | &map[errors.Field]string{
50 | errors.FieldMessage: "The password store is not accessible",
51 | errors.FieldAction: "configure",
52 | errors.FieldError: err.Error(),
53 | errors.FieldStoreID: store.ID,
54 | errors.FieldStoreName: store.Name,
55 | errors.FieldStorePath: store.Path,
56 | },
57 | )
58 | }
59 |
60 | store.Path = normalizedStorePath
61 |
62 | responseData.StoreSettings[store.ID], err = readDefaultSettings(store.Path)
63 | if err == nil {
64 | var storeSettings StoreSettings
65 | err = json.Unmarshal([]byte(responseData.StoreSettings[store.ID]), &storeSettings)
66 | }
67 | if err != nil {
68 | log.Errorf(
69 | "Unable to read .browserpass.json of the user-configured password store '%+v': %+v",
70 | store, err,
71 | )
72 | response.SendErrorAndExit(
73 | errors.CodeUnreadablePasswordStoreDefaultSettings,
74 | &map[errors.Field]string{
75 | errors.FieldMessage: "Unable to read .browserpass.json of the password store",
76 | errors.FieldAction: "configure",
77 | errors.FieldError: err.Error(),
78 | errors.FieldStoreID: store.ID,
79 | errors.FieldStoreName: store.Name,
80 | errors.FieldStorePath: store.Path,
81 | },
82 | )
83 | }
84 | }
85 |
86 | // Check whether a store in the default location exists and is accessible.
87 | // If there is at least one store in the settings, user will not use the default store => skip its validation.
88 | // However, if there are no stores in the settings, user expects to use the default password store => return an error if it is not accessible.
89 | if len(request.Settings.Stores) == 0 {
90 | possibleDefaultStorePath, err := getDefaultPasswordStorePath()
91 | if err != nil {
92 | log.Error("Unable to determine the location of the default password store: ", err)
93 | response.SendErrorAndExit(
94 | errors.CodeUnknownDefaultPasswordStoreLocation,
95 | &map[errors.Field]string{
96 | errors.FieldMessage: "Unable to determine the location of the default password store",
97 | errors.FieldAction: "configure",
98 | errors.FieldError: err.Error(),
99 | },
100 | )
101 | } else {
102 | responseData.DefaultStore.Path, err = normalizePasswordStorePath(possibleDefaultStorePath)
103 | if err != nil {
104 | log.Errorf(
105 | "The default password store is not accessible at the location '%v': %+v",
106 | possibleDefaultStorePath, err,
107 | )
108 | response.SendErrorAndExit(
109 | errors.CodeInaccessibleDefaultPasswordStore,
110 | &map[errors.Field]string{
111 | errors.FieldMessage: "The default password store is not accessible",
112 | errors.FieldAction: "configure",
113 | errors.FieldError: err.Error(),
114 | errors.FieldStorePath: possibleDefaultStorePath,
115 | },
116 | )
117 | }
118 | }
119 |
120 | responseData.DefaultStore.Settings, err = readDefaultSettings(responseData.DefaultStore.Path)
121 | if err == nil {
122 | var storeSettings StoreSettings
123 | err = json.Unmarshal([]byte(responseData.DefaultStore.Settings), &storeSettings)
124 | }
125 | if err != nil {
126 | log.Errorf(
127 | "Unable to read .browserpass.json of the default password store in '%v': %+v",
128 | responseData.DefaultStore.Path, err,
129 | )
130 | response.SendErrorAndExit(
131 | errors.CodeUnreadableDefaultPasswordStoreDefaultSettings,
132 | &map[errors.Field]string{
133 | errors.FieldMessage: "Unable to read .browserpass.json of the default password store",
134 | errors.FieldAction: "configure",
135 | errors.FieldError: err.Error(),
136 | errors.FieldStorePath: responseData.DefaultStore.Path,
137 | },
138 | )
139 | }
140 | }
141 |
142 | response.SendOk(responseData)
143 | }
144 |
145 | func getDefaultPasswordStorePath() (string, error) {
146 | path := os.Getenv("PASSWORD_STORE_DIR")
147 | if path != "" {
148 | return path, nil
149 | }
150 |
151 | home, err := os.UserHomeDir()
152 | if err != nil {
153 | return "", err
154 | }
155 |
156 | path = filepath.Join(home, ".password-store")
157 | return path, nil
158 | }
159 |
160 | func readDefaultSettings(storePath string) (string, error) {
161 | content, err := ioutil.ReadFile(filepath.Join(storePath, ".browserpass.json"))
162 | if err == nil {
163 | return string(content), nil
164 | }
165 | if os.IsNotExist(err) {
166 | return "{}", nil
167 | }
168 | return "", err
169 | }
170 |
--------------------------------------------------------------------------------
/PROTOCOL.md:
--------------------------------------------------------------------------------
1 | # Browserpass Communication Protocol
2 |
3 | This document describes the protocol used for communication between the browser extension,
4 | and the native host application.
5 |
6 | ## Response Types
7 |
8 | ### OK
9 |
10 | Consists solely of an `ok` status, an integer app version, and a `data` field which
11 | may be of any type.
12 |
13 | The app version is an integer, calculated by `(MAJOR * 1000000) + (MINOR * 1000) + PATCH`.
14 |
15 | ```
16 | {
17 | "status": "ok",
18 | "version": ,
19 | "data":
20 | }
21 | ```
22 |
23 | ### Error
24 |
25 | Consists solely of an `error` status, a nonzero integer error code, and an optional `params`
26 | object that provides any parameters that should accompany the error.
27 |
28 | ```
29 | {
30 | "status": "error",
31 | "code": ,
32 | "version": ,
33 | "params": {
34 | "":
35 | }
36 | }
37 | ```
38 |
39 | When the error message is relaying a message from a native system component, then that message
40 | should be supplied as a `message` parameter.
41 |
42 | ## List of Error Codes
43 |
44 | | Code | Description | Parameters |
45 | | ---- | ----------------------------------------------------------------------- | ---------------------------------------------------------------- |
46 | | 10 | Unable to parse browser request length | message, error |
47 | | 11 | Unable to parse browser request | message, error |
48 | | 12 | Invalid request action | message, action |
49 | | 13 | Inaccessible user-configured password store | message, action, error, storeId, storePath, storeName |
50 | | 14 | Inaccessible default password store | message, action, error, storePath |
51 | | 15 | Unable to determine the location of the default password store | message, action, error |
52 | | 16 | Unable to read the default settings of a user-configured password store | message, action, error, storeId, storePath, storeName |
53 | | 17 | Unable to read the default settings of the default password store | message, action, error, storePath |
54 | | 18 | Unable to list files in a password store | message, action, error, storeId, storePath, storeName |
55 | | 19 | Unable to determine a relative path for a file in a password store | message, action, error, storeId, storePath, storeName, file |
56 | | 20 | Invalid password store ID | message, action, storeId |
57 | | 21 | Invalid gpg path | message, action, error, gpgPath |
58 | | 22 | Unable to detect the location of the gpg binary | message, action, error |
59 | | 23 | Invalid password file extension | message, action, file |
60 | | 24 | Unable to decrypt the password file | message, action, error, storeId, storePath, storeName, file |
61 | | 25 | Unable to list directories in a password store | message, action, error, storeId, storePath, storeName |
62 | | 26 | Unable to determine a relative path for a directory in a password store | message, action, error, storeId, storePath, storeName, directory |
63 | | 27 | The entry contents is missing | message, action |
64 | | 28 | Unable to determine the recepients for the gpg encryption | message, action, error, storeId, storePath, storeName, file |
65 | | 29 | Unable to encrypt the password file | message, action, error, storeId, storePath, storeName, file |
66 | | 30 | Unable to delete the password file | message, action, error, storeId, storePath, storeName, file |
67 | | 31 | Unable to determine if directory is empty and can be deleted | message, action, error, storeId, storePath, storeName, directory |
68 | | 32 | Unable to delete the empty directory | message, action, error, storeId, storePath, storeName, directory |
69 |
70 | ## Settings
71 |
72 | The `settings` object is a key / value map of individual settings. It's provided by the
73 | browser to the native app as part of every request.
74 |
75 | Settings are saved in browser local storage. Each top-level setting is saved separately,
76 | JSON-encoded and saved by its key.
77 |
78 | Settings may also be supplied via a `.browserpass.json` file in the root of a password store,
79 | and via parameters in individual `*.gpg` files.
80 |
81 | Settings are applied using the following priority, highest first:
82 |
83 | 1. Configured by the user in specific `*.gpg` files (e.g. autosubmit: true)
84 | 1. Configured by the user in `.browserpass.json` file in specific password stores
85 | 1. Configured by the user via the extension options
86 | 1. Defaults shipped with the browser extension
87 |
88 | ### Global Settings
89 |
90 | | Setting | Description | Default |
91 | | ------- | ---------------------------------------------------- | ------- |
92 | | gpgPath | Optional path to gpg binary | `null` |
93 | | stores | List of password stores with store-specific settings | `{}` |
94 |
95 | ### Store-specific Settings
96 |
97 | | Setting | Description | Default |
98 | | ------- | --------------------------------------- | ------- |
99 | | id | Unique store id (same as the store key) | `` |
100 | | name | Store name | `""` |
101 | | path | Path to the password store directory | `""` |
102 |
103 | ## Actions
104 |
105 | ### Configure
106 |
107 | Returns a response containing the per-store config. Used to check that the host app
108 | is alive, determine the version at startup, and provide per-store defaults.
109 |
110 | #### Request
111 |
112 | ```
113 | {
114 | "settings": ,
115 | "defaultStoreSettings": ,
116 | "action": "configure"
117 | }
118 | ```
119 |
120 | #### Response
121 |
122 | ```
123 | {
124 |
125 | "status": "ok",
126 | "version": ,
127 | "data": {
128 | "defaultStore": {
129 | "path": "/path/to/default/store",
130 | "settings": "",
131 | },
132 | "storeSettings": {
133 | "storeId": ""
134 | }
135 | }
136 | }
137 | ```
138 |
139 | ### List
140 |
141 | Get a list of all `*.gpg` files for each of a provided array of directory paths. The `storeN`
142 | is the ID of a password store, the key in `"settings.stores"` object.
143 |
144 | #### Request
145 |
146 | ```
147 | {
148 | "settings": ,
149 | "action": "list"
150 | }
151 | ```
152 |
153 | #### Response
154 |
155 | ```
156 | {
157 | "status": "ok",
158 | "version": ,
159 | "data": {
160 | "files": {
161 | "storeN": ["", "<...>"],
162 | "storeN+1": ["", "<...>"]
163 | }
164 | }
165 | }
166 | ```
167 |
168 | ### Tree
169 |
170 | Get a list of all nested directories for each of a provided array of directory paths. The `storeN`
171 | is the ID of a password store, the key in `"settings.stores"` object.
172 |
173 | #### Request
174 |
175 | ```
176 | {
177 | "settings": ,
178 | "action": "tree"
179 | }
180 | ```
181 |
182 | #### Response
183 |
184 | ```
185 | {
186 | "status": "ok",
187 | "version": ,
188 | "data": {
189 | "directories": {
190 | "storeN": ["", "<...>"],
191 | "storeN+1": ["", "<...>"]
192 | }
193 | }
194 | }
195 | ```
196 |
197 | ### Fetch
198 |
199 | Get the decrypted contents of a specific file.
200 |
201 | #### Request
202 |
203 | ```
204 | {
205 | "settings": ,
206 | "action": "fetch",
207 | "storeId": "",
208 | "file": "relative/path/to/file.gpg"
209 | }
210 | ```
211 |
212 | #### Response
213 |
214 | ```
215 | {
216 | "status": "ok",
217 | "version": ,
218 | "data": {
219 | "contents": ""
220 | }
221 | }
222 | ```
223 |
224 | ### Save
225 |
226 | Encrypt the given contents and save to a specific file.
227 |
228 | #### Request
229 |
230 | ```
231 | {
232 | "settings": ,
233 | "action": "save",
234 | "storeId": "",
235 | "file": "relative/path/to/file.gpg",
236 | "contents": ""
237 | }
238 | ```
239 |
240 | #### Response
241 |
242 | ```
243 | {
244 | "status": "ok",
245 | "version":
246 | }
247 | ```
248 |
249 | ### Delete
250 |
251 | Delete a specific file and empty parent directories caused by the deletion, if any.
252 |
253 | #### Request
254 |
255 | ```
256 | {
257 | "settings": ,
258 | "action": "delete",
259 | "storeId": "",
260 | "file": "relative/path/to/file.gpg"
261 | }
262 | ```
263 |
264 | #### Response
265 |
266 | ```
267 | {
268 | "status": "ok",
269 | "version":
270 | }
271 | ```
272 |
273 | ### Echo
274 |
275 | Send the `echoResponse` in the request as a response.
276 |
277 | #### Request
278 |
279 | ```
280 | {
281 | "action": "echo",
282 | "echoResponse":
283 | }
284 | ```
285 |
286 | #### Response
287 |
288 | ```
289 |
290 | ```
291 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Browserpass - native messaging host
4 |
5 | This is a host application for [browserpass](https://github.com/browserpass/browserpass-extension) browser extension providing it access to your password store. The communication is handled through [Native Messaging API](https://developer.chrome.com/extensions/nativeMessaging).
6 |
7 | ## Table of Contents
8 |
9 | - [Installation](#installation)
10 | - [Install via package manager](#install-via-package-manager)
11 | - [Install manually](#install-manually)
12 | - [Install on Nix / NixOS](#install-on-nix--nixos)
13 | - [Install on Windows](#install-on-windows)
14 | - [Install on Windows through WSL](#install-on-windows-through-wsl)
15 | - [Configure browsers](#configure-browsers)
16 | - [Building the app](#building-the-app)
17 | - [Build locally](#build-locally)
18 | - [Build using Docker](#build-using-docker)
19 | - [Updates](#updates)
20 | - [FAQ](#faq)
21 | - [Error: Unable to fetch and parse login fields](#error-unable-to-fetch-and-parse-login-fields)
22 | - [Uninstallation](#uninstallation)
23 | - [Contributing](#contributing)
24 |
25 | ## Installation
26 |
27 | ### Install via package manager
28 |
29 | The following operating systems provide a browserpass package that can be installed using a package manager:
30 |
31 | - Arch Linux: [browserpass](https://www.archlinux.org/packages/extra/x86_64/browserpass/)
32 | - Gentoo Linux: [browserpass](https://packages.gentoo.org/packages/www-plugins/browserpass)
33 | - Debian sid: [webext-browserpass](https://packages.debian.org/sid/webext-browserpass) (note: users report ([#126](https://github.com/browserpass/browserpass-native/issues/126)) that it's not suitable for any browser besides Chromium and Firefox, use manual installation if you plan to use other browsers. Moreover, the latest version in the repos is `3.0.7` and the package seems to have been abandoned, see [browserpass-extension#369](https://github.com/browserpass/browserpass-extension/issues/369). Manual installation is advised).
34 | - openSUSE Tumbleweed: [browserpass-native](https://software.opensuse.org/package/browserpass-native)
35 | - NixOS: [browserpass](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/security/browserpass/default.nix) - also read [Install on Nix / NixOS](#install-on-nix--nixos)
36 | - macOS (Homebrew): [browserpass](https://github.com/Amar1729/homebrew-formulae/blob/master/Formula/browserpass.rb) in a user-contributed tap [amar1729/formulae](https://github.com/amar1729/homebrew-formulae)
37 | ```sh
38 | brew tap amar1729/formulae
39 | brew install browserpass # this will print further installation instructions, e.g. for Firefox:
40 | PREFIX='/opt/homebrew/opt/browserpass' make hosts-firefox-user -f '/opt/homebrew/opt/browserpass/lib/browserpass/Makefile'
41 | ```
42 |
43 | Once the package is installed, **refer to the section [Configure browsers](#configure-browsers)**.
44 |
45 | If your OS is not listed above, proceed with the manual installation steps below.
46 |
47 | ### Install manually
48 |
49 | Download [the latest Github release](https://github.com/browserpass/browserpass-native/releases), choose either the source code archive (if you want to compile the app yourself) or an archive for your operating system (it contains a pre-built binary).
50 |
51 | All release files are signed with a PGP key that is available on [maximbaz.com](https://maximbaz.com/), [keybase.io](https://keybase.io/maximbaz) and various OpenPGP key servers. First, import the public key using any of these commands:
52 |
53 | ```
54 | $ curl https://maximbaz.com/pgp_keys.asc | gpg --import
55 | $ curl https://keybase.io/maximbaz/pgp_keys.asc | gpg --import
56 | $ gpg --recv-keys 56C3E775E72B0C8B1C0C1BD0B5DB77409B11B601
57 | ```
58 |
59 | To verify the signature of a given file, use `$ gpg --verify .asc`.
60 |
61 | It should report:
62 |
63 | ```
64 | gpg: Signature made ...
65 | gpg: using EDDSA key 04D7A219B0ABE4C2B62A5E654A2B758631E1FD91
66 | gpg: Good signature from "Maxim Baz <...>"
67 | gpg: aka ...
68 | Primary key fingerprint: 56C3 E775 E72B 0C8B 1C0C 1BD0 B5DB 7740 9B11 B601
69 | Subkey fingerprint: 04D7 A219 B0AB E4C2 B62A 5E65 4A2B 7586 31E1 FD91
70 | ```
71 |
72 | Unpack the archive. If you decided to compile the application yourself, refer to the [Building the app](#building-the-app) section on how to do so. Once complete, continue with the steps below.
73 |
74 | If you are on macOS, first install the necessary tools: `brew install coreutils gnu-sed`.
75 |
76 | If you are on FreeBSD, first install the GNU tools: `pkg install coreutils gmake gsed'.` Use `gmake` in place of `make` below.
77 |
78 | If you downloaded a release archive with pre-compiled binary, follow these steps to install the app:
79 |
80 | ```
81 | # IMPORTANT: replace XXXX with OS name depending on the archive you downloaded, e.g. "linux64"
82 | make BIN=browserpass-XXXX configure # Configure the hosts json files
83 | sudo make BIN=browserpass-XXXX install # Install the app
84 | ```
85 |
86 | In addition, both `configure` and `install` targets respect `PREFIX`, `DESTDIR` parameters if you want to customize the install location (e.g. to install to a `$HOME` dir to avoid using `sudo`).
87 |
88 | For example, if you are on macOS or FreeBSD, you probably want to install Browserpass in `/usr/local/bin`, therefore you need to run:
89 |
90 | ```
91 | make BIN=browserpass-darwin64 PREFIX=/usr/local configure # Configure the hosts json files
92 | sudo make BIN=browserpass-darwin64 PREFIX=/usr/local install # Install the app
93 | ```
94 |
95 | If you compiled the app yourself, you can omit `BIN` parameter:
96 |
97 | ```
98 | make configure # Configure the hosts json files
99 | sudo make install # Install the app
100 | ```
101 |
102 | Finally proceed to the [Configure browsers](#configure-browsers) section.
103 |
104 | #### Install on Nix / NixOS
105 |
106 | For a declarative NixOS installation, update your channel with `sudo nix-channel --update`, use the following to your `/etc/nixos/configuration.nix` and rebuild your system:
107 |
108 | ```nix
109 | { pkgs, ... }: {
110 | programs.browserpass.enable = true;
111 | environment.systemPackages = with pkgs; [
112 | # All of these browsers will work
113 | chromium firefox google-chrome vivaldi
114 | # firefox*-bin versions do *not* work with this. If you require such Firefox versions, use the stateful setup described below.
115 | ];
116 | }
117 | ```
118 |
119 | For a stateful Nix setup, update your channel, install Browserpass and link the necessary files with the Makefile (see [Configure browsers](#configure-browsers) section), but pass `DESTDIR=~/.nix-profile`:
120 |
121 | ```bash
122 | $ nix-channel --update
123 | $ nix-env -iA nixpkgs.browserpass # Or nix-env -iA nixos.browserpass on NixOS
124 | $ DESTDIR=~/.nix-profile make -f ~/.nix-profile/lib/browserpass/Makefile
125 | ```
126 |
127 | #### Install on Windows
128 |
129 | Download [the latest Github release](https://github.com/browserpass/browserpass-native/releases/latest) for `windows64`.
130 |
131 | Run the installer, it will install all the necessary files in `C:\Program Files\Browserpass` and it will write in the Windows registry to [configure browsers](#configure-browsers).
132 | Browserpass will look for the password store in `C:\Users\\.password-store` by default. For troubleshooting, the log file can be found in `C:\Users\\AppData\Local\browserpass`.
133 |
134 | #### Install on Windows through WSL
135 |
136 | If you want to use WSL instead
137 |
138 | 1. Follow the [installation](#installation) steps for the WSL distribution you are using. There is no need to configure the browser as your browser does not run in WSL.
139 | 2. Follow the then [installation](#install-on-windows) steps for installing on Windows.
140 | 3. Create `C:\Program Files\Browserpass\browserpass-wsl.bat` with the following contents:
141 |
142 | ```
143 | @echo off
144 | bash -c "/usr/bin/browserpass-linux64 2>/dev/null"
145 | ```
146 |
147 | 4. Edit the hosts json files (in our example `C:\Program Files\Browserpass\browser-files\*-host.json`) and replace `browserpass-windows64.exe` with `browserpass-wsl.bat` you've just created.
148 |
149 | Remember to check [Hints for configuring gpg](#error-unable-to-fetch-and-parse-login-fields) on how to configure pinentry to unlock your PGP key.
150 |
151 | ### Configure browsers
152 |
153 | #### Unix-like Systems
154 |
155 | The following operating systems provide packages for certain browsers that can be installed using a package manager:
156 |
157 | - Arch Linux: [browserpass-chromium](https://www.archlinux.org/packages/extra/any/browserpass-chromium/) and [browserpass-firefox](https://www.archlinux.org/packages/extra/any/browserpass-firefox/)
158 | - AUR: [browserpass-chrome](https://aur.archlinux.org/packages/browserpass-chrome/)
159 |
160 | If you installed a distro package above, you are done!
161 |
162 | If something went wrong, if there's no package for your OS and/or a browser of your choice, or for whatever reason you just don't want to use them, proceed with the steps below.
163 |
164 | First, enter the directory with installed Browserpass, by default it is `/usr/lib/browserpass/`, but if you used `PREFIX` or `DESTDIR` when running `make install`, it might be different for you. For example, on macOS the directory is likely to be `/usr/local/lib/browserpass/`.
165 |
166 | See below the list of available `make` goals to configure various browsers. Use `gmake` on FreeBSD in place of `make`.
167 |
168 | **It is recommended to use `*-user` make goals**, as more people had luck with them. But if they don't work as expected, try other available goals.
169 |
170 | If you provided `PREFIX` and/or `DESTDIR` while running `make install`, remember that you must provide the same parameters, for example `make PREFIX=/usr/local hosts-chromium-user`:
171 |
172 | | Command | Description |
173 | | --------------------------- | ------------------------------------------------------------------------------------ |
174 | | `make hosts-chromium-user` | Configure browserpass for Chromium browser, for the current user only |
175 | | `make hosts-firefox-user` | Configure browserpass for Firefox browser, for the current user only |
176 | | `make hosts-librewolf-user` | Configure browserpass for Librewolf browser, for the current user only |
177 | | `make hosts-chrome-user` | Configure browserpass for Google Chrome or Opera browsers, for the current user only |
178 | | `make hosts-edge-user` | Configure browserpass for Microsoft Edge browser, for the current user only |
179 | | `make hosts-brave-user` | Configure browserpass for Brave browser, for the current user only |
180 | | `make hosts-iridium-user` | Configure browserpass for Iridium browser, for the current user only |
181 | | `make hosts-vivaldi-user` | Configure browserpass for Vivaldi browser, for the current user only |
182 | | `make hosts-yandex-user` | Configure browserpass for Yandex browser, for the current user only |
183 | | `make hosts-slimjet-user` | Configure browserpass for Slimjet browser, for the current user only |
184 | | `make hosts-arc-user` | Configure browserpass for Arc browser, for the current user only |
185 | | `sudo make hosts-chromium` | Configure browserpass for Chromium browser, system-wide |
186 | | `sudo make hosts-firefox` | Configure browserpass for Firefox browser, system-wide |
187 | | `sudo make hosts-librewolf` | Configure browserpass for Librewolf browser, system-wide |
188 | | `sudo make hosts-chrome` | Configure browserpass for Google Chrome or Opera browsers, system-wide |
189 | | `sudo make hosts-edge` | Configure browserpass for Microsoft Edge browser, system-wide |
190 | | `sudo make hosts-brave` | Configure browserpass for Brave browser, system-wide |
191 | | `sudo make hosts-iridium` | Configure browserpass for Iridium browser, system-wide |
192 | | `sudo make hosts-vivaldi` | Configure browserpass for Vivaldi browser, system-wide |
193 | | `sudo make hosts-yandex` | Configure browserpass for Yandex browser, system-wide |
194 | | `sudo make hosts-slimjet` | Configure browserpass for Slimjet browser, system-wide |
195 | | `sudo make hosts-arc` | Configure browserpass for Arc browser, system-wide |
196 |
197 | In addition, Chromium-based browsers support the following `make` goals:
198 |
199 | | Command | Description |
200 | | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
201 | | `make policies-chromium-user` | Automatically install browser extension from Web Store for Chromium browser, for the current user only |
202 | | `make policies-chrome-user` | Automatically install browser extension from Web Store for Google Chrome browser, for the current user only |
203 | | `make policies-edge-user` | Automatically install browser extension from Web Store for Microsoft Edge browser, for the current user only |
204 | | `make policies-brave-user` | Automatically install browser extension from Web Store for Brave browser, for the current user only |
205 | | `make policies-iridium-user` | Automatically install browser extension from Web Store for Iridium browser, for the current user only |
206 | | `make policies-slimjet-user` | Automatically install browser extension from Web Store for Slimjet browser, for the current user only |
207 | | `make policies-vivaldi-user` | Automatically install browser extension from Web Store for Vivaldi browser, for the current user only |
208 | | `make policies-yandex-user` | Automatically install browser extension from Web Store for Yandex browser, for the current user only |
209 | | `make policies-arc-user` | Automatically install browser extension from Web Store for Arc browser, for the current user only |
210 | | `sudo make policies-chromium` | Automatically install browser extension from Web Store for Chromium browser, system-wide |
211 | | `sudo make policies-chrome` | Automatically install browser extension from Web Store for Google Chrome browser, system-wide |
212 | | `sudo make policies-edge` | Automatically install browser extension from Web Store for Microsoft Edge browser, system-wide |
213 | | `sudo make policies-brave` | Automatically install browser extension from Web Store for Brave browser, system-wide |
214 | | `sudo make policies-iridium` | Automatically install browser extension from Web Store for Iridium browser, system-wide |
215 | | `sudo make policies-slimjet` | Automatically install browser extension from Web Store for Slimjet browser, system-wide |
216 | | `sudo make policies-vivaldi` | Automatically install browser extension from Web Store for Vivaldi browser, system-wide |
217 | | `sudo make policies-yandex` | Automatically install browser extension from Web Store for Yandex browser, system-wide |
218 | | `sudo make policies-arc` | Automatically install browser extension from Web Store for Arc browser, system-wide |
219 |
220 | #### Windows
221 |
222 | The browser will fetch the configuration file from the registry, checking for the full path to the manifest file (such as `firefox-host.json`) in the registry in either:
223 | - (Firefox) `Software\Mozilla\NativeMessagingHosts\com.github.browserpass.native\(default)`
224 | - (Chromium-based) `Software\Google\Chrome\NativeMessagingHosts\com.github.browserpass.native\(default)`
225 |
226 | This should be automatically done for you if you ran the released installer.
227 |
228 | ## Building the app
229 |
230 | ### Build locally
231 |
232 | Make sure you have the latest stable Go installed.
233 |
234 | The following `make` goals are available (check Makefile for more details):
235 |
236 | | Command | Description |
237 | | ---------------------------- | ----------------------------------- |
238 | | `make` or `make all` | Compile the app and run tests |
239 | | `make browserpass` | Compile the app for your OS |
240 | | `make browserpass-linux64` | Compile the app for Linux 64-bit |
241 | | `make browserpass-windows64` | Compile the app for Windows 64-bit |
242 | | `make browserpass-darwin64` | Compile the app for Mac OS X 64-bit |
243 | | `make browserpass-openbsd64` | Compile the app for OpenBSD 64-bit |
244 | | `make browserpass-freebsd64` | Compile the app for FreeBSD 64-bit |
245 | | `make test` | Run tests |
246 |
247 | ### Build using Docker
248 |
249 | First build the docker image using the following command in the project root:
250 |
251 | ```shell
252 | docker build -t browserpass-native .
253 | ```
254 |
255 | The entry point in the docker image is the `make` command. To run it:
256 |
257 | ```shell
258 | docker run --rm -v "$(pwd)":/src browserpass-native
259 | ```
260 |
261 | Specify `make` goal(s) as the last parameter, for example:
262 |
263 | ```shell
264 | docker run --rm -v "$(pwd)":/src browserpass-native test
265 | ```
266 |
267 | Refer to the list of available `make` goals above.
268 |
269 | ## Updates
270 |
271 | If you installed the app using a package manager for your OS, you will likely update it in the same way.
272 |
273 | If you installed manually, repeat the steps in the [Install manually](#install-manually) section.
274 |
275 | ## FAQ
276 |
277 | ### Error: Unable to fetch and parse login fields
278 |
279 | If you can see passwords, but unable to fill forms or copy credentials, you likely have issues with your `gpg` setup.
280 |
281 | First things first, make sure that `gpg` and some GUI `pinentry` are installed.
282 |
283 | - on macOS many people succeeded with `pinentry-mac`
284 | - on Linux [users report](https://github.com/browserpass/browserpass-extension/issues/155) that `pinentry-gnome3` does not work well with GNOME 3 and Firefox, use e.g. `pinentry-gtk-2`
285 | - on Windows WSL people succeded with [pinentry-wsl-ps1](https://github.com/diablodale/pinentry-wsl-ps1)
286 |
287 | `pinentry` is the application that asks you your password to unlock PGP key when you for example use `pass`.
288 |
289 | The selected `pinentry` **must have GUI**, console-based (like `pinentry-tty` or `pinentry-curses`) **are not supported** (unless you know what you are doing).
290 |
291 | Ensure that `gpg-agent` process is actually running, if not you need to investigate how to enable it.
292 |
293 | Finally configure a GUI pinentry program in `~/.gnupg/gpg-agent.conf`:
294 |
295 | ```
296 | pinentry-program /full/path/to/pinentry
297 | ```
298 |
299 | You will need to restart `gpg-agent` using: `$ gpgconf --kill gpg-agent`
300 |
301 | If Browserpass is unable to locate the proper `gpg` binary, try configuring a full path to your `gpg` in the browser extension settings or in `.browserpass.json` file in the root of your password store:
302 |
303 | ```json
304 | {
305 | "gpgPath": "/full/path/to/gpg"
306 | }
307 | ```
308 |
309 | ### Setup on NixOS-WSL
310 |
311 | In the case of [NixOS-WSL](https://github.com/nix-community/NixOS-WSL) (or any WSL instance where the `bash` PowerShell command raises a `CreateProcessEntryCommon` error), the content of `C:\Program Files\Browserpass\browserpass-wsl.bat` needs a slight modification since `browserpass` needs to be executed through the [NixOS magic occurring in the `root` login shell](https://github.com/nix-community/NixOS-WSL/issues/284):
312 |
313 | ```powershell
314 | @echo off
315 | wsl -u user -- "/usr/bin/browserpass 2>/dev/null"
316 | ```
317 |
318 | With the `wsl` command, you can also specify the WSL distribution executing the command:
319 |
320 | ```powershell
321 | wsl -d NixOS -u user -- "/usr/bin/browserpass 2>/dev/null"
322 | ```
323 |
324 | This is especially handy if your NixOS WSL is not the default distribution.
325 |
326 | It's worth noting that if Browserpass is installed in the user environment, the path should be as follows:
327 |
328 | ```powershell
329 | @echo off
330 | wsl -u user -- "/home/user/.nix-profile/bin/browserpass 2>/dev/null"
331 | ```
332 |
333 | ## Uninstallation
334 |
335 | If you installed Browserpass via a package manager, uninstalling the package will hopefully do all the necessary cleanup.
336 |
337 | For manual installations, uninstallation procedure basically consists of following the steps you ran in Makefile in reverse.
338 |
339 | ## Contributing
340 |
341 | 1. Fork [the repo](https://github.com/browserpass/browserpass-extension)
342 | 2. Create your feature branch
343 | - `git checkout -b my-new-feature`
344 | 3. Commit your changes
345 | - `git commit -am 'Add some feature'`
346 | 4. Push the branch
347 | - `git push origin my-new-feature`
348 | 5. Create a new pull request
349 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BIN ?= browserpass
2 | VERSION = $(shell cat .version)
3 |
4 | PREFIX ?= /usr
5 | BIN_DIR = $(DESTDIR)$(PREFIX)/bin
6 | LIB_DIR = $(DESTDIR)$(PREFIX)/lib
7 | SHARE_DIR = $(DESTDIR)$(PREFIX)/share
8 | XDG_CONFIG_HOME ?= $(HOME)/.config
9 |
10 | BIN_PATH = $(BIN_DIR)/$(BIN)
11 | BIN_PATH_WINDOWS = C:\\\\\\\\\\\\\\\\Program Files\\\\\\\\\\\\\\\\Browserpass\\\\\\\\\\\\\\\\browserpass-windows64.exe
12 |
13 | # -buildmode=pie requires CGO on riscv64
14 | ifeq ($(shell uname -m), riscv64)
15 | export CGO_ENABLED := 1
16 | else
17 | export CGO_ENABLED := 0
18 | endif
19 | GOFLAGS := -buildmode=pie -trimpath
20 |
21 | APP_ID = com.github.browserpass.native
22 | OS = $(shell uname -s)
23 |
24 | # GNU tools
25 | SED = $(shell which gsed 2>/dev/null || which sed 2>/dev/null)
26 | INSTALL = $(shell which ginstall 2>/dev/null || which install 2>/dev/null)
27 |
28 | #######################
29 | # For local development
30 |
31 | .PHONY: all
32 | all: browserpass test
33 |
34 | browserpass: *.go **/*.go
35 | env GOFLAGS="$(GOFLAGS)" go build -o $@
36 |
37 | browserpass-linux64: *.go **/*.go
38 | env GOOS=linux GOARCH=amd64 go build -o $@
39 |
40 | browserpass-arm: *.go **/*.go
41 | env GOOS=linux GOARCH=arm go build -o $@
42 |
43 | browserpass-arm64: *.go **/*.go
44 | env GOOS=linux GOARCH=arm64 go build -o $@
45 |
46 | browserpass-darwin64: *.go **/*.go
47 | env GOOS=darwin GOARCH=amd64 go build -o $@
48 |
49 | browserpass-darwin-arm64: *.go **/*.go
50 | env GOOS=darwin GOARCH=arm64 go build -o $@
51 |
52 | browserpass-openbsd64: *.go **/*.go
53 | env GOOS=openbsd GOARCH=amd64 go build -o $@
54 |
55 | browserpass-freebsd64: *.go **/*.go
56 | env GOOS=freebsd GOARCH=amd64 go build -o $@
57 |
58 | browserpass-dragonfly64: *.go **/*.go
59 | env GOOS=dragonfly GOARCH=amd64 go build -o $@
60 |
61 | browserpass-windows64: *.go **/*.go
62 | env GOOS=windows GOARCH=amd64 go build -o $@.exe
63 |
64 | browserpass-windows: *.go **/*.go
65 | env GOOS=windows GOARCH=386 go build -o $@.exe
66 |
67 | .PHONY: test
68 | test:
69 | go test ./...
70 |
71 | #######################
72 | # For official releases
73 |
74 | .PHONY: vendor
75 | vendor:
76 | go mod tidy
77 | go mod vendor
78 |
79 | .PHONY: clean
80 | clean:
81 | rm -f browserpass browserpass-*
82 | rm -rf dist
83 | rm -rf vendor
84 |
85 | .PHONY: dist
86 | dist: clean vendor browserpass-linux64 browserpass-arm browserpass-arm64 browserpass-darwin64 browserpass-darwin-arm64 browserpass-openbsd64 browserpass-freebsd64 browserpass-dragonfly64 browserpass-windows64 browserpass-windows
87 | $(eval TMP := $(shell mktemp -d))
88 |
89 | # Full source code
90 | mkdir "$(TMP)/browserpass-native-$(VERSION)"
91 | cp -r * "$(TMP)/browserpass-native-$(VERSION)"
92 | (cd "$(TMP)" && tar -cvzf "browserpass-native-$(VERSION)-src.tar.gz" "browserpass-native-$(VERSION)")
93 |
94 | # Unix installers
95 | for os in linux64 arm arm64 darwin64 darwin-arm64 openbsd64 freebsd64 dragonfly64; do \
96 | mkdir $(TMP)/browserpass-"$$os"-$(VERSION); \
97 | cp -a browserpass-"$$os"* browser-files Makefile README.md LICENSE $(TMP)/browserpass-"$$os"-$(VERSION); \
98 | (cd $(TMP) && tar -cvzf browserpass-"$$os"-$(VERSION).tar.gz browserpass-"$$os"-$(VERSION)); \
99 | done
100 |
101 | # Windows installer
102 | mkdir $(TMP)/browserpass-windows64-$(VERSION)
103 | cp -a browserpass-windows64.exe browser-files Makefile README.md LICENSE windows-setup.wxs $(TMP)/browserpass-windows64-$(VERSION)
104 | (cd $(TMP)/browserpass-windows64-$(VERSION); \
105 | make BIN_PATH="$(BIN_PATH_WINDOWS)" configure; \
106 | wixl --verbose --arch x64 windows-setup.wxs --output ../browserpass-windows64-$(VERSION).msi)
107 |
108 | mkdir -p dist
109 | mv "$(TMP)/"*.tar.gz "$(TMP)/"*.msi dist
110 | git -c tar.tar.gz.command="gzip -cn" archive -o dist/browserpass-native-$(VERSION).tar.gz --format tar.gz --prefix=browserpass-native-$(VERSION)/ v$(VERSION)
111 |
112 | for file in dist/*; do \
113 | gpg --detach-sign --armor "$$file"; \
114 | done
115 |
116 | rm -rf $(TMP)
117 | rm -f dist/browserpass-native-$(VERSION).tar.gz
118 |
119 | #######################
120 | # For user installation
121 |
122 | .PHONY: configure
123 | configure:
124 | $(SED) -i 's|"path": ".*"|"path": "'"$(BIN_PATH)"'"|' browser-files/chromium-host.json
125 | $(SED) -i 's|"path": ".*"|"path": "'"$(BIN_PATH)"'"|' browser-files/firefox-host.json
126 |
127 | .PHONY: install
128 | install:
129 | $(INSTALL) -Dm755 -t "$(BIN_DIR)/" $(BIN)
130 | $(INSTALL) -Dm644 -t "$(LIB_DIR)/browserpass/" Makefile
131 | $(INSTALL) -Dm644 -t "$(SHARE_DIR)/licenses/browserpass/" LICENSE
132 | $(INSTALL) -Dm644 -t "$(SHARE_DIR)/doc/browserpass/" README.md
133 |
134 | $(INSTALL) -Dm644 browser-files/chromium-host.json "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json"
135 | $(INSTALL) -Dm644 browser-files/chromium-policy.json "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json"
136 | $(INSTALL) -Dm644 browser-files/firefox-host.json "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json"
137 |
138 | # Browser-specific hosts targets
139 |
140 | .PHONY: hosts-chromium
141 | hosts-chromium:
142 | @case $(OS) in \
143 | Linux) \
144 | mkdir -p "/etc/chromium/native-messaging-hosts/"; \
145 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/chromium/native-messaging-hosts/$(APP_ID).json"; \
146 | [ -e "/etc/chromium/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
147 | ;; \
148 | Darwin) \
149 | mkdir -p "/Library/Application Support/Chromium/NativeMessagingHosts/"; \
150 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json"; \
151 | [ -e "/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
152 | ;; \
153 | *) \
154 | echo "The operating system $(OS) is not supported"; \
155 | exit 1; \
156 | ;; \
157 | esac
158 |
159 | .PHONY: hosts-chromium-user
160 | hosts-chromium-user:
161 | @case $(OS) in \
162 | Linux|*BSD|DragonFly) \
163 | mkdir -p "$(XDG_CONFIG_HOME)/chromium/NativeMessagingHosts/"; \
164 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/chromium/NativeMessagingHosts/$(APP_ID).json"; \
165 | [ -e "$(XDG_CONFIG_HOME)/chromium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
166 | ;; \
167 | Darwin) \
168 | mkdir -p "${HOME}/Library/Application Support/Chromium/NativeMessagingHosts/"; \
169 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json"; \
170 | [ -e "${HOME}/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
171 | ;; \
172 | *) \
173 | echo "The operating system $(OS) is not supported"; \
174 | exit 1; \
175 | ;; \
176 | esac
177 |
178 | .PHONY: hosts-chrome
179 | hosts-chrome:
180 | @case $(OS) in \
181 | Linux) \
182 | mkdir -p "/etc/opt/chrome/native-messaging-hosts/"; \
183 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/opt/chrome/native-messaging-hosts/$(APP_ID).json"; \
184 | [ -e "/etc/opt/chrome/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
185 | ;; \
186 | Darwin) \
187 | mkdir -p "/Library/Google/Chrome/NativeMessagingHosts/"; \
188 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Google/Chrome/NativeMessagingHosts/$(APP_ID).json"; \
189 | [ -e "/Library/Google/Chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
190 | ;; \
191 | *) \
192 | echo "The operating system $(OS) is not supported"; \
193 | exit 1; \
194 | ;; \
195 | esac
196 |
197 | .PHONY: hosts-chrome-user
198 | hosts-chrome-user:
199 | @case $(OS) in \
200 | Linux|*BSD|DragonFly) \
201 | mkdir -p "$(XDG_CONFIG_HOME)/google-chrome/NativeMessagingHosts/"; \
202 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/google-chrome/NativeMessagingHosts/$(APP_ID).json"; \
203 | [ -e "$(XDG_CONFIG_HOME)/google-chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
204 | ;; \
205 | Darwin) \
206 | mkdir -p "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/"; \
207 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json"; \
208 | [ -e "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
209 | ;; \
210 | *) \
211 | echo "The operating system $(OS) is not supported"; \
212 | exit 1; \
213 | ;; \
214 | esac
215 |
216 | .PHONY: hosts-arc
217 | hosts-arc:
218 | @case $(OS) in \
219 | Darwin) \
220 | mkdir -p "/Library/Application Support/Arc/User Data/NativeMessagingHosts/"; \
221 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Arc/User Data/NativeMessagingHosts/$(APP_ID).json"; \
222 | [ -e "/Library/Application Support/Arc/User Data/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
223 | ;; \
224 | *) \
225 | echo "The operating system $(OS) is not supported"; \
226 | exit 1; \
227 | ;; \
228 | esac
229 |
230 | .PHONY: hosts-arc-user
231 | hosts-arc-user:
232 | @case $(OS) in \
233 | Darwin) \
234 | mkdir -p "${HOME}/Library/Application Support/Arc/User Data/NativeMessagingHosts/"; \
235 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Arc/User Data/NativeMessagingHosts/$(APP_ID).json"; \
236 | [ -e "${HOME}/Library/Application Support/Arc/User Data/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
237 | ;; \
238 | *) \
239 | echo "The operating system $(OS) is not supported"; \
240 | exit 1; \
241 | ;; \
242 | esac
243 |
244 | .PHONY: hosts-edge
245 | hosts-edge:
246 | @case $(OS) in \
247 | # Linux) \
248 | # mkdir -p "/opt/microsoft/msedge/native-messaging-hosts/"; \
249 | # ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/opt/microsoft/msedge/native-messaging-hosts/$(APP_ID).json"; \
250 | # [ -e "/opt/microsoft/msedge/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
251 | # ;; \
252 | # Darwin) \
253 | # mkdir -p "/Library/Google/Chrome/NativeMessagingHosts/"; \
254 | # ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Google/Chrome/NativeMessagingHosts/$(APP_ID).json"; \
255 | # [ -e "/Library/Google/Chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
256 | # ;; \
257 | *) \
258 | echo "The operating system $(OS) is not supported"; \
259 | exit 1; \
260 | ;; \
261 | esac
262 |
263 | .PHONY: hosts-edge-user
264 | hosts-edge-user:
265 | @case $(OS) in \
266 | Linux|*BSD|DragonFly) \
267 | mkdir -p "$(XDG_CONFIG_HOME)/microsoft-edge/NativeMessagingHosts/"; \
268 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/microsoft-edge/NativeMessagingHosts/$(APP_ID).json"; \
269 | [ -e "$(XDG_CONFIG_HOME)/microsoft-edge/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
270 | ;; \
271 | # Darwin) \
272 | # mkdir -p "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/"; \
273 | # ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json"; \
274 | # [ -e "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
275 | # ;; \
276 | *) \
277 | echo "The operating system $(OS) is not supported"; \
278 | exit 1; \
279 | ;; \
280 | esac
281 |
282 | .PHONY: hosts-vivaldi
283 | hosts-vivaldi:
284 | @case $(OS) in \
285 | Linux) \
286 | mkdir -p "/etc/opt/vivaldi/native-messaging-hosts/"; \
287 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/opt/vivaldi/native-messaging-hosts/$(APP_ID).json"; \
288 | [ -e "/etc/opt/vivaldi/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
289 | ;; \
290 | Darwin) \
291 | mkdir -p "/Library/Application Support/Vivaldi/NativeMessagingHosts/"; \
292 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Vivaldi/NativeMessagingHosts/$(APP_ID).json"; \
293 | [ -e "/Library/Application Support/Vivaldi/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
294 | ;; \
295 | *) \
296 | echo "The operating system $(OS) is not supported"; \
297 | exit 1; \
298 | ;; \
299 | esac
300 |
301 | .PHONY: hosts-vivaldi-user
302 | hosts-vivaldi-user:
303 | @case $(OS) in \
304 | Linux|*BSD|DragonFly) \
305 | mkdir -p "$(XDG_CONFIG_HOME)/vivaldi/NativeMessagingHosts/"; \
306 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/vivaldi/NativeMessagingHosts/$(APP_ID).json"; \
307 | [ -e "$(XDG_CONFIG_HOME)/vivaldi/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
308 | ;; \
309 | Darwin) \
310 | mkdir -p "${HOME}/Library/Application Support/Vivaldi/NativeMessagingHosts/"; \
311 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Vivaldi/NativeMessagingHosts/$(APP_ID).json"; \
312 | [ -e "${HOME}/Library/Application Support/Vivaldi/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
313 | ;; \
314 | *) \
315 | echo "The operating system $(OS) is not supported"; \
316 | exit 1; \
317 | ;; \
318 | esac
319 |
320 | .PHONY: hosts-yandex
321 | hosts-yandex:
322 | @case $(OS) in \
323 | Linux) \
324 | mkdir -p "/etc/opt/yandex-browser/native-messaging-hosts/"; \
325 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/opt/yandex-browser/native-messaging-hosts/$(APP_ID).json"; \
326 | [ -e "/etc/opt/yandex-browser/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
327 | ;; \
328 | Darwin) \
329 | mkdir -p "/Library/Application Support/Yandex/NativeMessagingHosts/"; \
330 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Yandex/NativeMessagingHosts/$(APP_ID).json"; \
331 | [ -e "/Library/Application Support/Yandex/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
332 | ;; \
333 | *) \
334 | echo "The operating system $(OS) is not supported"; \
335 | exit 1; \
336 | ;; \
337 | esac
338 |
339 | .PHONY: hosts-yandex-user
340 | hosts-yandex-user:
341 | @case $(OS) in \
342 | Linux|*BSD|DragonFly) \
343 | mkdir -p "$(XDG_CONFIG_HOME)/yandex-browser/NativeMessagingHosts/"; \
344 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/yandex-browser/NativeMessagingHosts/$(APP_ID).json"; \
345 | [ -e "$(XDG_CONFIG_HOME)/yandex-browser/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
346 | ;; \
347 | Darwin) \
348 | mkdir -p "${HOME}/Library/Application Support/Yandex/NativeMessagingHosts/"; \
349 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Yandex/NativeMessagingHosts/$(APP_ID).json"; \
350 | [ -e "${HOME}/Library/Application Support/Yandex/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
351 | ;; \
352 | *) \
353 | echo "The operating system $(OS) is not supported"; \
354 | exit 1; \
355 | ;; \
356 | esac
357 |
358 | .PHONY: hosts-brave
359 | hosts-brave:
360 | @case $(OS) in \
361 | Linux) \
362 | mkdir -p "/etc/opt/chrome/native-messaging-hosts/"; \
363 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/opt/chrome/native-messaging-hosts/$(APP_ID).json"; \
364 | [ -e "/etc/opt/chrome/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
365 | ;; \
366 | Darwin) \
367 | mkdir -p "/Library/Application Support/Chromium/NativeMessagingHosts/"; \
368 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json"; \
369 | [ -e "/Library/Application Support/Chromium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
370 | ;; \
371 | *) \
372 | echo "The operating system $(OS) is not supported"; \
373 | exit 1; \
374 | ;; \
375 | esac
376 |
377 | .PHONY: hosts-brave-user
378 | hosts-brave-user:
379 | @case $(OS) in \
380 | Linux|*BSD|DragonFly) \
381 | mkdir -p "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/NativeMessagingHosts/"; \
382 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/NativeMessagingHosts/$(APP_ID).json"; \
383 | [ -e "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
384 | ;; \
385 | Darwin) \
386 | mkdir -p "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/"; \
387 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json"; \
388 | [ -e "${HOME}/Library/Application Support/Google/Chrome/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
389 | ;; \
390 | *) \
391 | echo "The operating system $(OS) is not supported"; \
392 | exit 1; \
393 | ;; \
394 | esac
395 |
396 | .PHONY: hosts-iridium
397 | hosts-iridium:
398 | @case $(OS) in \
399 | Linux) \
400 | mkdir -p "/etc/iridium-browser/native-messaging-hosts/"; \
401 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/iridium-browser/native-messaging-hosts/$(APP_ID).json"; \
402 | [ -e "/etc/iridium-browser/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
403 | ;; \
404 | Darwin) \
405 | mkdir -p "/Library/Application Support/Iridium/NativeMessagingHosts/"; \
406 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Iridium/NativeMessagingHosts/$(APP_ID).json"; \
407 | [ -e "/Library/Application Support/Iridium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
408 | ;; \
409 | *) \
410 | echo "The operating system $(OS) is not supported"; \
411 | exit 1; \
412 | ;; \
413 | esac
414 |
415 | .PHONY: hosts-iridium-user
416 | hosts-iridium-user:
417 | @case $(OS) in \
418 | Linux|*BSD|DragonFly) \
419 | mkdir -p "$(XDG_CONFIG_HOME)/iridium/NativeMessagingHosts/"; \
420 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/iridium/NativeMessagingHosts/$(APP_ID).json"; \
421 | [ -e "$(XDG_CONFIG_HOME)/iridium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
422 | ;; \
423 | Darwin) \
424 | mkdir -p "${HOME}/Library/Application Support/Iridium/NativeMessagingHosts/"; \
425 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Iridium/NativeMessagingHosts/$(APP_ID).json"; \
426 | [ -e "${HOME}/Library/Application Support/Iridium/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
427 | ;; \
428 | *) \
429 | echo "The operating system $(OS) is not supported"; \
430 | exit 1; \
431 | ;; \
432 | esac
433 |
434 | .PHONY: hosts-slimjet
435 | hosts-slimjet:
436 | @case $(OS) in \
437 | Linux) \
438 | mkdir -p "/etc/opt/slimjet/native-messaging-hosts/"; \
439 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/etc/opt/slimjet/native-messaging-hosts/$(APP_ID).json"; \
440 | [ -e "/etc/opt/slimjet/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
441 | ;; \
442 | Darwin) \
443 | mkdir -p "/Library/Application Support/Slimjet/NativeMessagingHosts/"; \
444 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "/Library/Application Support/Slimjet/NativeMessagingHosts/$(APP_ID).json"; \
445 | [ -e "/Library/Application Support/Slimjet/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
446 | ;; \
447 | *) \
448 | echo "The operating system $(OS) is not supported"; \
449 | exit 1; \
450 | ;; \
451 | esac
452 |
453 | .PHONY: hosts-slimjet-user
454 | hosts-slimjet-user:
455 | @case $(OS) in \
456 | Linux|*BSD|DragonFly) \
457 | mkdir -p "${HOME}/.config/slimject/NativeMessagingHosts/"; \
458 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/.config/slimject/NativeMessagingHosts/$(APP_ID).json"; \
459 | [ -e "${HOME}/.config/slimject/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
460 | ;; \
461 | Darwin) \
462 | mkdir -p "${HOME}/Library/Application Support/Slimjet/NativeMessagingHosts/"; \
463 | ln -sfv "$(LIB_DIR)/browserpass/hosts/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Slimjet/NativeMessagingHosts/$(APP_ID).json"; \
464 | [ -e "${HOME}/Library/Application Support/Slimjet/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
465 | ;; \
466 | *) \
467 | echo "The operating system $(OS) is not supported"; \
468 | exit 1; \
469 | ;; \
470 | esac
471 |
472 | .PHONY: hosts-firefox
473 | hosts-firefox:
474 | @case $(OS) in \
475 | Linux) \
476 | mkdir -p "$(LIB_DIR)/mozilla/native-messaging-hosts/"; \
477 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "/usr/lib/mozilla/native-messaging-hosts/$(APP_ID).json"; \
478 | [ -e "/usr/lib/mozilla/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
479 | ;; \
480 | Darwin) \
481 | mkdir -p "/Library/Application Support/Mozilla/NativeMessagingHosts/"; \
482 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "/Library/Application Support/Mozilla/NativeMessagingHosts/$(APP_ID).json"; \
483 | [ -e "/Library/Application Support/Mozilla/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
484 | ;; \
485 | *) \
486 | echo "The operating system $(OS) is not supported"; \
487 | exit 1; \
488 | ;; \
489 | esac
490 |
491 | .PHONY: hosts-firefox-user
492 | hosts-firefox-user:
493 | @case $(OS) in \
494 | Linux|*BSD|DragonFly) \
495 | mkdir -p "${HOME}/.mozilla/native-messaging-hosts/"; \
496 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "${HOME}/.mozilla/native-messaging-hosts/$(APP_ID).json"; \
497 | [ -e "${HOME}/.mozilla/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
498 | ;; \
499 | Darwin) \
500 | mkdir -p "${HOME}/Library/Application Support/Mozilla/NativeMessagingHosts/"; \
501 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "${HOME}/Library/Application Support/Mozilla/NativeMessagingHosts/$(APP_ID).json"; \
502 | [ -e "${HOME}/Library/Application Support/Mozilla/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
503 | ;; \
504 | *) \
505 | echo "The operating system $(OS) is not supported"; \
506 | exit 1; \
507 | ;; \
508 | esac
509 |
510 | .PHONY: hosts-librewolf
511 | hosts-librewolf:
512 | @case $(OS) in \
513 | Linux) \
514 | mkdir -p "$(LIB_DIR)/librewolf/native-messaging-hosts/"; \
515 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "/usr/lib/librewolf/native-messaging-hosts/$(APP_ID).json"; \
516 | [ -e "/usr/lib/librewolf/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
517 | ;; \
518 | Darwin) \
519 | mkdir -p "/Library/Application Support/librewolf/NativeMessagingHosts/"; \
520 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "/Library/Application Support/librewolf/NativeMessagingHosts/$(APP_ID).json"; \
521 | [ -e "/Library/Application Support/librewolf/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
522 | ;; \
523 | *) \
524 | echo "The operating system $(OS) is not supported"; \
525 | exit 1; \
526 | ;; \
527 | esac
528 |
529 | .PHONY: hosts-librewolf-user
530 | hosts-librewolf-user:
531 | @case $(OS) in \
532 | Linux|*BSD|DragonFly) \
533 | mkdir -p "${HOME}/.librewolf/native-messaging-hosts/"; \
534 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "${HOME}/.librewolf/native-messaging-hosts/$(APP_ID).json"; \
535 | [ -e "${HOME}/.librewolf/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
536 | ;; \
537 | Darwin) \
538 | mkdir -p "${HOME}/Library/Application Support/librewolf/NativeMessagingHosts/"; \
539 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "${HOME}/Library/Application Support/librewolf/NativeMessagingHosts/$(APP_ID).json"; \
540 | [ -e "${HOME}/Library/Application Support/librewolf/NativeMessagingHosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
541 | ;; \
542 | *) \
543 | echo "The operating system $(OS) is not supported"; \
544 | exit 1; \
545 | ;; \
546 | esac
547 |
548 | .PHONY: hosts-waterfox
549 | hosts-waterfox:
550 | @case $(OS) in \
551 | Linux) \
552 | mkdir -p "$(LIB_DIR)/waterfox/native-messaging-hosts/"; \
553 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "/usr/lib/waterfox/native-messaging-hosts/$(APP_ID).json"; \
554 | [ -e "/usr/lib/waterfox/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
555 | ;; \
556 | *) \
557 | echo "The operating system $(OS) is not supported"; \
558 | exit 1; \
559 | ;; \
560 | esac
561 |
562 | .PHONY: hosts-waterfox-user
563 | hosts-waterfox-user:
564 | @case $(OS) in \
565 | Linux|*BSD|DragonFly) \
566 | mkdir -p "${HOME}/.waterfox/native-messaging-hosts/"; \
567 | ln -sfv "$(LIB_DIR)/browserpass/hosts/firefox/$(APP_ID).json" "${HOME}/.waterfox/native-messaging-hosts/$(APP_ID).json"; \
568 | [ -e "${HOME}/.waterfox/native-messaging-hosts/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
569 | ;; \
570 | *) \
571 | echo "The operating system $(OS) is not supported"; \
572 | exit 1; \
573 | ;; \
574 | esac
575 |
576 | # Browser-specific policies targets
577 |
578 | .PHONY: policies-chromium
579 | policies-chromium:
580 | @case $(OS) in \
581 | Linux) \
582 | mkdir -p "/etc/chromium/policies/managed/"; \
583 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/chromium/policies/managed/$(APP_ID).json"; \
584 | [ -e "/etc/chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
585 | ;; \
586 | Darwin) \
587 | mkdir -p "/Library/Application Support/Chromium/policies/managed/"; \
588 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json"; \
589 | [ -e "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
590 | ;; \
591 | *) \
592 | echo "The operating system $(OS) is not supported"; \
593 | exit 1; \
594 | ;; \
595 | esac
596 |
597 | .PHONY: policies-chromium-user
598 | policies-chromium-user:
599 | @case $(OS) in \
600 | Linux|*BSD|DragonFly) \
601 | mkdir -p "$(XDG_CONFIG_HOME)/chromium/policies/managed/"; \
602 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/chromium/policies/managed/$(APP_ID).json"; \
603 | [ -e "$(XDG_CONFIG_HOME)/chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
604 | ;; \
605 | Darwin) \
606 | mkdir -p "${HOME}/Library/Application Support/Chromium/policies/managed/"; \
607 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Chromium/policies/managed/$(APP_ID).json"; \
608 | [ -e "${HOME}/Library/Application Support/Chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
609 | ;; \
610 | *) \
611 | echo "The operating system $(OS) is not supported"; \
612 | exit 1; \
613 | ;; \
614 | esac
615 |
616 | .PHONY: policies-arc
617 | policies-arc:
618 | @case $(OS) in \
619 | Darwin) \
620 | mkdir -p "/Library/Application Support/Arc/User Data/policies/managed/"; \
621 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Arc/User Data/policies/managed/$(APP_ID).json"; \
622 | [ -e "/Library/Application Support/Arc/User Data/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
623 | ;; \
624 | *) \
625 | echo "The operating system $(OS) is not supported"; \
626 | exit 1; \
627 | ;; \
628 | esac
629 |
630 | .PHONY: policies-arc-user
631 | policies-arc-user:
632 | @case $(OS) in \
633 | Darwin) \
634 | mkdir -p "${HOME}/Library/Application Support/Arc/User Data/policies/managed/"; \
635 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Arc/User Data/policies/managed/$(APP_ID).json"; \
636 | [ -e "${HOME}/Library/Application Support/Arc/User Data/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
637 | ;; \
638 | *) \
639 | echo "The operating system $(OS) is not supported"; \
640 | exit 1; \
641 | ;; \
642 | esac
643 |
644 | .PHONY: policies-chrome
645 | policies-chrome:
646 | @case $(OS) in \
647 | Linux) \
648 | mkdir -p "/etc/opt/chrome/policies/managed/"; \
649 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/chrome/policies/managed/$(APP_ID).json"; \
650 | [ -e "/etc/opt/chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
651 | ;; \
652 | Darwin) \
653 | mkdir -p "/Library/Google/Chrome/policies/managed/"; \
654 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Google/Chrome/policies/managed/$(APP_ID).json"; \
655 | [ -e "/Library/Google/Chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
656 | ;; \
657 | *) \
658 | echo "The operating system $(OS) is not supported"; \
659 | exit 1; \
660 | ;; \
661 | esac
662 |
663 | .PHONY: policies-chrome-user
664 | policies-chrome-user:
665 | @case $(OS) in \
666 | Linux|*BSD|DragonFly) \
667 | mkdir -p "$(XDG_CONFIG_HOME)/google-chrome/policies/managed/"; \
668 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/google-chrome/policies/managed/$(APP_ID).json"; \
669 | [ -e "$(XDG_CONFIG_HOME)/google-chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
670 | ;; \
671 | Darwin) \
672 | mkdir -p "${HOME}/Library/Application Support/Google/Chrome/policies/managed/"; \
673 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json"; \
674 | [ -e "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
675 | ;; \
676 | *) \
677 | echo "The operating system $(OS) is not supported"; \
678 | exit 1; \
679 | ;; \
680 | esac
681 |
682 | .PHONY: policies-edge
683 | policies-edge:
684 | @case $(OS) in \
685 | # Linux) \
686 | # mkdir -p "/etc/opt/chrome/policies/managed/"; \
687 | # ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/chrome/policies/managed/$(APP_ID).json"; \
688 | # [ -e "/etc/opt/chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
689 | # ;; \
690 | # Darwin) \
691 | # mkdir -p "/Library/Google/Chrome/policies/managed/"; \
692 | # ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Google/Chrome/policies/managed/$(APP_ID).json"; \
693 | # [ -e "/Library/Google/Chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
694 | # ;; \
695 | *) \
696 | echo "The operating system $(OS) is not supported"; \
697 | exit 1; \
698 | ;; \
699 | esac
700 |
701 | .PHONY: policies-edge-user
702 | policies-edge-user:
703 | @case $(OS) in \
704 | # Linux|*BSD|DragonFly) \
705 | # mkdir -p "$(XDG_CONFIG_HOME)/microsoft-edge/policies/managed/"; \
706 | # ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/microsoft-edge/policies/managed/$(APP_ID).json"; \
707 | # [ -e "$(XDG_CONFIG_HOME)/microsoft-edge/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
708 | # ;; \
709 | # Darwin) \
710 | # mkdir -p "${HOME}/Library/Application Support/Google/Chrome/policies/managed/"; \
711 | # ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json"; \
712 | # [ -e "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
713 | # ;; \
714 | *) \
715 | echo "The operating system $(OS) is not supported"; \
716 | exit 1; \
717 | ;; \
718 | esac
719 |
720 | .PHONY: policies-vivaldi
721 | policies-vivaldi:
722 | @case $(OS) in \
723 | Linux) \
724 | mkdir -p "/etc/opt/vivaldi/policies/managed/"; \
725 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/vivaldi/policies/managed/$(APP_ID).json"; \
726 | [ -e "/etc/opt/vivaldi/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
727 | ;; \
728 | Darwin) \
729 | mkdir -p "/Library/Application Support/Vivaldi/policies/managed/"; \
730 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Vivaldi/policies/managed/$(APP_ID).json"; \
731 | [ -e "/Library/Application Support/Vivaldi/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
732 | ;; \
733 | *) \
734 | echo "The operating system $(OS) is not supported"; \
735 | exit 1; \
736 | ;; \
737 | esac
738 |
739 | .PHONY: policies-vivaldi-user
740 | policies-vivaldi-user:
741 | @case $(OS) in \
742 | Linux|*BSD|DragonFly) \
743 | mkdir -p "$(XDG_CONFIG_HOME)/vivaldi/policies/managed/"; \
744 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/vivaldi/policies/managed/$(APP_ID).json"; \
745 | [ -e "$(XDG_CONFIG_HOME)/vivaldi/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
746 | ;; \
747 | Darwin) \
748 | mkdir -p "${HOME}/Library/Application Support/Vivaldi/policies/managed/"; \
749 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Vivaldi/policies/managed/$(APP_ID).json"; \
750 | [ -e "${HOME}/Library/Application Support/Vivaldi/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
751 | ;; \
752 | *) \
753 | echo "The operating system $(OS) is not supported"; \
754 | exit 1; \
755 | ;; \
756 | esac
757 |
758 | .PHONY: policies-yandex
759 | policies-yandex:
760 | @case $(OS) in \
761 | Linux) \
762 | mkdir -p "/etc/opt/yandex-browser/policies/managed/"; \
763 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/yandex-browser/policies/managed/$(APP_ID).json"; \
764 | [ -e "/etc/opt/yandex-browser/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
765 | ;; \
766 | Darwin) \
767 | mkdir -p "/Library/Application Support/Yandex/policies/managed/"; \
768 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Yandex/policies/managed/$(APP_ID).json"; \
769 | [ -e "/Library/Application Support/Yandex/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
770 | ;; \
771 | *) \
772 | echo "The operating system $(OS) is not supported"; \
773 | exit 1; \
774 | ;; \
775 | esac
776 |
777 | .PHONY: policies-yandex-user
778 | policies-yandex-user:
779 | @case $(OS) in \
780 | Linux|*BSD|DragonFly) \
781 | mkdir -p "$(XDG_CONFIG_HOME)/yandex-browser/policies/managed/"; \
782 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/yandex-browser/policies/managed/$(APP_ID).json"; \
783 | [ -e "$(XDG_CONFIG_HOME)/yandex-browser/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
784 | ;; \
785 | Darwin) \
786 | mkdir -p "${HOME}/Library/Application Support/Yandex/policies/managed/"; \
787 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Yandex/policies/managed/$(APP_ID).json"; \
788 | [ -e "${HOME}/Library/Application Support/Yandex/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
789 | ;; \
790 | *) \
791 | echo "The operating system $(OS) is not supported"; \
792 | exit 1; \
793 | ;; \
794 | esac
795 |
796 | .PHONY: policies-brave
797 | policies-brave:
798 | @case $(OS) in \
799 | Linux) \
800 | mkdir -p "/etc/opt/chrome/policies/managed/"; \
801 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/chrome/policies/managed/$(APP_ID).json"; \
802 | [ -e "/etc/opt/chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
803 | ;; \
804 | Darwin) \
805 | mkdir -p "/Library/Application Support/Chromium/policies/managed/"; \
806 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json"; \
807 | [ -e "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
808 | ;; \
809 | *) \
810 | echo "The operating system $(OS) is not supported"; \
811 | exit 1; \
812 | ;; \
813 | esac
814 |
815 | .PHONY: policies-brave-user
816 | policies-brave-user:
817 | @case $(OS) in \
818 | Linux|*BSD|DragonFly) \
819 | mkdir -p "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/policies/managed/"; \
820 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/policies/managed/$(APP_ID).json"; \
821 | [ -e "$(XDG_CONFIG_HOME)/BraveSoftware/Brave-Browser/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
822 | ;; \
823 | Darwin) \
824 | mkdir -p "${HOME}/Library/Application Support/Google/Chrome/policies/managed/"; \
825 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json"; \
826 | [ -e "${HOME}/Library/Application Support/Google/Chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
827 | ;; \
828 | *) \
829 | echo "The operating system $(OS) is not supported"; \
830 | exit 1; \
831 | ;; \
832 | esac
833 |
834 | .PHONY: policies-iridium
835 | policies-iridium:
836 | @case $(OS) in \
837 | Linux) \
838 | mkdir -p "/etc/opt/chrome/policies/managed/"; \
839 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/chrome/policies/managed/$(APP_ID).json"; \
840 | [ -e "/etc/opt/chrome/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
841 | ;; \
842 | Darwin) \
843 | mkdir -p "/Library/Application Support/Chromium/policies/managed/"; \
844 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json"; \
845 | [ -e "/Library/Application Support/Chromium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
846 | ;; \
847 | *) \
848 | echo "The operating system $(OS) is not supported"; \
849 | exit 1; \
850 | ;; \
851 | esac
852 |
853 | .PHONY: policies-iridium-user
854 | policies-iridium-user:
855 | @case $(OS) in \
856 | Linux|*BSD|DragonFly) \
857 | mkdir -p "$(XDG_CONFIG_HOME)/iridium/policies/managed/"; \
858 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "$(XDG_CONFIG_HOME)/iridium/policies/managed/$(APP_ID).json"; \
859 | [ -e "$(XDG_CONFIG_HOME)/iridium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
860 | ;; \
861 | Darwin) \
862 | mkdir -p "${HOME}/Library/Application Support/Iridium/policies/managed/"; \
863 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Iridium/policies/managed/$(APP_ID).json"; \
864 | [ -e "${HOME}/Library/Application Support/Iridium/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
865 | ;; \
866 | *) \
867 | echo "The operating system $(OS) is not supported"; \
868 | exit 1; \
869 | ;; \
870 | esac
871 |
872 | .PHONY: policies-slimjet
873 | policies-slimjet:
874 | @case $(OS) in \
875 | Linux) \
876 | mkdir -p "/etc/opt/slimjet/policies/managed/"; \
877 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/etc/opt/slimjet/policies/managed/$(APP_ID).json"; \
878 | [ -e "/etc/opt/slimjet/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
879 | ;; \
880 | Darwin) \
881 | mkdir -p "/Library/Application Support/Slimjet/policies/managed/"; \
882 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "/Library/Application Support/Slimjet/policies/managed/$(APP_ID).json"; \
883 | [ -e "/Library/Application Support/Slimjet/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
884 | ;; \
885 | *) \
886 | echo "The operating system $(OS) is not supported"; \
887 | exit 1; \
888 | ;; \
889 | esac
890 |
891 | .PHONY: policies-slimjet-user
892 | policies-slimjet-user:
893 | @case $(OS) in \
894 | Linux|*BSD|DragonFly) \
895 | mkdir -p "${HOME}/.config/slimjet/policies/managed/"; \
896 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/.config/slimjet/policies/managed/$(APP_ID).json"; \
897 | [ -e "${HOME}/.config/slimjet/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
898 | ;; \
899 | Darwin) \
900 | mkdir -p "${HOME}/Library/Application Support/Slimjet/policies/managed/"; \
901 | ln -sfv "$(LIB_DIR)/browserpass/policies/chromium/$(APP_ID).json" "${HOME}/Library/Application Support/Slimjet/policies/managed/$(APP_ID).json"; \
902 | [ -e "${HOME}/Library/Application Support/Slimjet/policies/managed/$(APP_ID).json" ] || echo "Error: the symlink points to a non-existent location" >&2; \
903 | ;; \
904 | *) \
905 | echo "The operating system $(OS) is not supported"; \
906 | exit 1; \
907 | ;; \
908 | esac
909 |
--------------------------------------------------------------------------------