From fcc7514ec5f1b35068b1033d8ac4278c45043a80 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 8 Jun 2018 10:26:58 +0200 Subject: [feat] pure go bitmask vpn implemenation - Resolves: #42 --- Makefile | 13 ++-- bitmask/bitmask.go | 29 ++++++++ bitmask/events.go | 50 ------------- bitmask/main.go | 161 ------------------------------------------ bitmask/vpn.go | 92 ------------------------ bitmask_go.go | 26 +++++++ bitmask_go/bonafide.go | 87 +++++++++++++++++++++++ bitmask_go/bonafide_test.go | 26 +++++++ bitmask_go/launcher_linux.go | 92 ++++++++++++++++++++++++ bitmask_go/main.go | 78 +++++++++++++++++++++ bitmask_go/status.go | 91 ++++++++++++++++++++++++ bitmask_go/vpn.go | 98 ++++++++++++++++++++++++++ bitmaskd.go | 26 +++++++ bitmaskd/events.go | 50 +++++++++++++ bitmaskd/main.go | 163 +++++++++++++++++++++++++++++++++++++++++++ bitmaskd/vpn.go | 92 ++++++++++++++++++++++++ main.go | 8 +-- systray.go | 12 ++-- 18 files changed, 877 insertions(+), 317 deletions(-) create mode 100644 bitmask/bitmask.go delete mode 100644 bitmask/events.go delete mode 100644 bitmask/main.go delete mode 100644 bitmask/vpn.go create mode 100644 bitmask_go.go create mode 100644 bitmask_go/bonafide.go create mode 100644 bitmask_go/bonafide_test.go create mode 100644 bitmask_go/launcher_linux.go create mode 100644 bitmask_go/main.go create mode 100644 bitmask_go/status.go create mode 100644 bitmask_go/vpn.go create mode 100644 bitmaskd.go create mode 100644 bitmaskd/events.go create mode 100644 bitmaskd/main.go create mode 100644 bitmaskd/vpn.go diff --git a/Makefile b/Makefile index 33a2d6c..74c0c44 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,20 @@ -.PHONY: all get build icon locales generate_locales clean +.PHONY: all get build build_go icon locales generate_locales clean + +TAGS ?= gtk_3_18 all: icon locales get build get: - go get -tags 'gtk_3_18' . + go get -tags $(TAGS) . ./bitmask_go build: - go build -tags 'gtk_3_18' -ldflags "-X main.version=`git describe --tags`" + go build -tags $(TAGS) -ldflags "-X main.version=`git describe --tags`" test: - go test -tags 'gtk_3_18' ./... + go test -tags $(TAGS) ./... + +build_go: + go build -tags "$(TAGS) bitmask_go" -ldflags "-X main.version=`git describe --tags`" clean: make -C icon clean diff --git a/bitmask/bitmask.go b/bitmask/bitmask.go new file mode 100644 index 0000000..f9b1cc9 --- /dev/null +++ b/bitmask/bitmask.go @@ -0,0 +1,29 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +type Bitmask interface { + GetStatusCh() <-chan string + Close() + Version() (string, error) + StartVPN(provider string) error + StopVPN() error + GetStatus() (string, error) + InstallHelpers() error + VPNCheck() (helpers bool, priviledge bool, err error) + ListGateways(provider string) ([]string, error) + UseGateway(name string) error +} diff --git a/bitmask/events.go b/bitmask/events.go deleted file mode 100644 index e97804c..0000000 --- a/bitmask/events.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2018 LEAP -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package bitmask - -import ( - "log" - "net/http" -) - -const ( - statusEvent = "VPN_STATUS_CHANGED" -) - -func (b *Bitmask) eventsHandler() { - b.send("events", "register", statusEvent) - client := &http.Client{ - Timeout: 0, - } - for { - resJSON, err := send(b.apiToken, client, "events", "poll") - res, ok := resJSON.([]interface{}) - if err != nil || !ok || len(res) < 1 { - continue - } - event, ok := res[0].(string) - if !ok || event != statusEvent { - continue - } - - status, err := b.GetStatus() - if err != nil { - log.Printf("Error receiving status: %v", err) - continue - } - b.statusCh <- status - } -} diff --git a/bitmask/main.go b/bitmask/main.go deleted file mode 100644 index e0bf4a0..0000000 --- a/bitmask/main.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (C) 2018 LEAP -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package bitmask - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "log" - "net/http" - "path" - "time" -) - -const ( - timeout = time.Second * 15 - url = "http://localhost:7070/API/" - headerAuth = "X-Bitmask-Auth" -) - -// Bitmask holds the bitmask client data -type Bitmask struct { - client *http.Client - apiToken string - statusCh chan string -} - -// Init the connection to bitmask -func Init() (*Bitmask, error) { - statusCh := make(chan string) - client := &http.Client{ - Timeout: timeout, - } - - err := waitForBitmaskd() - if err != nil { - return nil, err - } - - apiToken, err := getToken() - if err != nil { - return nil, err - } - - b := Bitmask{client, apiToken, statusCh} - go b.eventsHandler() - return &b, nil -} - -// GetStatusCh returns a channel that will recieve VPN status changes -func (b *Bitmask) GetStatusCh() chan string { - return b.statusCh -} - -// Close the connection to bitmask -func (b *Bitmask) Close() { - _, err := b.send("core", "stop") - if err != nil { - log.Printf("Got an error stopping bitmaskd: %v", err) - } -} - -// Version gets the bitmask version string -func (b *Bitmask) Version() (string, error) { - res, err := b.send("core", "version") - if err != nil { - return "", err - } - return res["version_core"].(string), nil -} - -func waitForBitmaskd() error { - var err error - for i := 0; i < 30; i++ { - resp, err := http.Post(url, "", nil) - if err == nil { - resp.Body.Close() - return nil - } - log.Printf("Bitmask is not ready (iteration %d): %v", i, err) - time.Sleep(1 * time.Second) - } - return err -} - -func (b *Bitmask) send(parts ...interface{}) (map[string]interface{}, error) { - resJSON, err := send(b.apiToken, b.client, parts...) - if err != nil { - return nil, err - } - result, ok := resJSON.(map[string]interface{}) - if !ok { - return nil, errors.New("Not valid response") - } - return result, nil -} - -func send(apiToken string, client *http.Client, parts ...interface{}) (interface{}, error) { - apiSection, _ := parts[0].(string) - reqBody, err := json.Marshal(parts[1:]) - if err != nil { - return nil, err - } - req, err := http.NewRequest("POST", url+apiSection, bytes.NewReader(reqBody)) - if err != nil { - return nil, err - } - req.Header.Add(headerAuth, apiToken) - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - resJSON, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return parseResponse(resJSON) -} - -func parseResponse(resJSON []byte) (interface{}, error) { - var response struct { - Result interface{} - Error string - } - err := json.Unmarshal(resJSON, &response) - if response.Error != "" { - return nil, errors.New(response.Error) - } - return response.Result, err -} - -func getToken() (string, error) { - var err error - path := path.Join(ConfigPath, "authtoken") - for i := 0; i < 30; i++ { - b, err := ioutil.ReadFile(path) - if err == nil { - return string(b), nil - } - log.Printf("Auth token is not ready (iteration %d): %v", i, err) - time.Sleep(1 * time.Second) - } - return "", err -} diff --git a/bitmask/vpn.go b/bitmask/vpn.go deleted file mode 100644 index aee5e8f..0000000 --- a/bitmask/vpn.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (C) 2018 LEAP -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package bitmask - -import ( - "errors" - "log" -) - -// StartVPN for provider -func (b *Bitmask) StartVPN(provider string) error { - _, err := b.send("vpn", "start", provider) - return err -} - -// StopVPN or cancel -func (b *Bitmask) StopVPN() error { - _, err := b.send("vpn", "stop") - return err -} - -// GetStatus returns the VPN status -func (b *Bitmask) GetStatus() (string, error) { - res, err := b.send("vpn", "status") - if err != nil { - return "", err - } - return res["status"].(string), nil -} - -// InstallHelpers into the system -func (b *Bitmask) InstallHelpers() error { - _, err := b.send("vpn", "install") - return err -} - -// VPNCheck returns if the helpers are installed and up to date and if polkit is running -func (b *Bitmask) VPNCheck() (helpers bool, priviledge bool, err error) { - res, err := b.send("vpn", "check", "") - if err != nil { - return false, false, err - } - installed, ok := res["installed"].(bool) - if !ok { - log.Printf("Unexpected value for installed on 'vpn check': %v", res) - return false, false, errors.New("Invalid response format") - } - privcheck, ok := res["privcheck"].(bool) - if !ok { - log.Printf("Unexpected value for privcheck on 'vpn check': %v", res) - return installed, false, errors.New("Invalid response format") - } - return installed, privcheck, nil -} - -// ListGateways return the names of the gateways -func (b *Bitmask) ListGateways(provider string) ([]string, error) { - res, err := b.send("vpn", "list") - if err != nil { - return nil, err - } - - names := []string{} - locations, ok := res[provider].([]interface{}) - if !ok { - return nil, errors.New("Can't read the locations for provider " + provider) - } - for i := range locations { - loc := locations[i].(map[string]interface{}) - names = append(names, loc["name"].(string)) - } - return names, nil -} - -// UseGateway selects name as the default gateway -func (b *Bitmask) UseGateway(name string) error { - _, err := b.send("vpn", "locations", name) - return err -} diff --git a/bitmask_go.go b/bitmask_go.go new file mode 100644 index 0000000..38ec289 --- /dev/null +++ b/bitmask_go.go @@ -0,0 +1,26 @@ +// +build bitmask_go +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "0xacab.org/leap/bitmask-systray/bitmask" + bitmask_go "0xacab.org/leap/bitmask-systray/bitmask_go" +) + +func initBitmask() (bitmask.Bitmask, error) { + return bitmask_go.Init() +} diff --git a/bitmask_go/bonafide.go b/bitmask_go/bonafide.go new file mode 100644 index 0000000..25fa302 --- /dev/null +++ b/bitmask_go/bonafide.go @@ -0,0 +1,87 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net/http" +) + +const ( + certAPI = "https://api.black.riseup.net/1/cert" +) + +var ( + caCert = []byte(`-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl +dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE +AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw +NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM +Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv +b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m +TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a +7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE +LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY +iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK +5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx +HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58 +m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF +PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q +hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj +shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k +ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu +f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD +VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB +AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v +qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/ +3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ +4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7 +3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch +Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf +Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg +tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF +tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ +UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp +0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO +-----END CERTIFICATE-----`) +) + +func getCertPem() ([]byte, error) { + certs := x509.NewCertPool() + certs.AppendCertsFromPEM(caCert) + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certs, + }, + }, + } + + resp, err := client.Post(certAPI, "", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("get vpn cert has failed with status: %s", resp.Status) + } + + return ioutil.ReadAll(resp.Body) +} diff --git a/bitmask_go/bonafide_test.go b/bitmask_go/bonafide_test.go new file mode 100644 index 0000000..c40c98d --- /dev/null +++ b/bitmask_go/bonafide_test.go @@ -0,0 +1,26 @@ +package bitmask + +import ( + "bytes" + "testing" +) + +var ( + privateKeyHeader = []byte("-----BEGIN RSA PRIVATE KEY-----") + certHeader = []byte("-----BEGIN CERTIFICATE-----") +) + +func TestGetCert(t *testing.T) { + cert, err := getCertPem() + if err != nil { + t.Fatal("get_cert returned an error: ", err) + } + + if !bytes.Contains(cert, privateKeyHeader) { + t.Errorf("No private key present: \n%q", cert) + } + + if !bytes.Equal(cert, certHeader) { + t.Errorf("No cert present: \n%q", cert) + } +} diff --git a/bitmask_go/launcher_linux.go b/bitmask_go/launcher_linux.go new file mode 100644 index 0000000..05398a6 --- /dev/null +++ b/bitmask_go/launcher_linux.go @@ -0,0 +1,92 @@ +// +build linux +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "errors" + "log" + "os" + "os/exec" +) + +const ( + systemOpenvpnPath = "/usr/sbin/openvpn" + snapOpenvpnPath = "/snap/bin/riseup-vpn.openvpn" +) + +var bitmaskRootPaths = []string{ + "/usr/sbin/bitmask-root", + "/usr/local/sbin/bitmask-root", + "/snap/bin/riseup-vpn.bitmask-root", +} + +func openvpnStart(flags ...string) error { + log.Println("openvpn start: ", flags) + arg := []string{"openvpn", "start", getOpenvpnPath()} + arg = append(arg, flags...) + // TODO: check errors somehow instead of fire and forget + go runBitmaskRoot(arg...) + return nil +} + +func openvpnStop() error { + log.Println("openvpn stop") + return runBitmaskRoot("openvpn", "stop") +} + +func firewallStart(gateways []string) error { + log.Println("firewall start") + arg := []string{"firewall", "start"} + arg = append(arg, gateways...) + return runBitmaskRoot(arg...) +} + +func firewallStop() error { + log.Println("firewall stop") + return runBitmaskRoot("firewall", "stop") +} + +func runBitmaskRoot(arg ...string) error { + bitmaskRoot, err := bitmaskRootPath() + if err != nil { + return err + } + arg = append([]string{bitmaskRoot}, arg...) + + cmd := exec.Command("pkexec", arg...) + err = cmd.Run() + if err != nil { + return err + } + return nil +} + +func bitmaskRootPath() (string, error) { + for _, path := range bitmaskRootPaths { + if _, err := os.Stat(path); !os.IsNotExist(err) { + return path, nil + } + } + return "", errors.New("No bitmask-root found") +} + +func getOpenvpnPath() string { + if os.Getenv("SNAP") != "" { + return snapOpenvpnPath + } + return systemOpenvpnPath +} diff --git a/bitmask_go/main.go b/bitmask_go/main.go new file mode 100644 index 0000000..49f803a --- /dev/null +++ b/bitmask_go/main.go @@ -0,0 +1,78 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "io/ioutil" + "log" + "os" + + "github.com/apparentlymart/go-openvpn-mgmt/openvpn" +) + +// Bitmask holds the bitmask client data +type Bitmask struct { + tempdir string + statusCh chan string + managementClient *openvpn.MgmtClient +} + +// Init the connection to bitmask +func Init() (*Bitmask, error) { + statusCh := make(chan string, 10) + tempdir, err := ioutil.TempDir("", "leap-") + if err != nil { + return nil, err + } + b := Bitmask{tempdir, statusCh, nil} + + err = b.StopVPN() + if err != nil { + return nil, err + } + + cert, err := getCertPem() + if err != nil { + return nil, err + } + err = ioutil.WriteFile(b.getCertPemPath(), cert, 0600) + if err != nil { + return nil, err + } + err = ioutil.WriteFile(b.getCaCertPath(), caCert, 0600) + + go b.openvpnManagement() + return &b, err +} + +// GetStatusCh returns a channel that will recieve VPN status changes +func (b *Bitmask) GetStatusCh() <-chan string { + return b.statusCh +} + +// Close the connection to bitmask +func (b *Bitmask) Close() { + b.StopVPN() + err := os.RemoveAll(b.tempdir) + if err != nil { + log.Printf("There was an error removing temp dir: %v", err) + } +} + +// Version gets the bitmask version string +func (b *Bitmask) Version() (string, error) { + return "", nil +} diff --git a/bitmask_go/status.go b/bitmask_go/status.go new file mode 100644 index 0000000..5a1ee3d --- /dev/null +++ b/bitmask_go/status.go @@ -0,0 +1,91 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "fmt" + "log" + + "github.com/apparentlymart/go-openvpn-mgmt/openvpn" +) + +const ( + On = "on" + Off = "off" + Starting = "starting" + Stopping = "stopping" + Failed = "failed" +) + +var statusNames = map[string]string{ + "CONNECTING": Starting, + "WAIT": Starting, + "AUTH": Starting, + "GET_CONFIG": Starting, + "ASSIGN_IP": Starting, + "ADD_ROUTES": Starting, + "CONNECTED": On, + "RECONNECTING": Starting, + "EXITING": Stopping, + "OFF": Off, + "FAILED": Off, +} + +func (b *Bitmask) openvpnManagement() { + // TODO: we should warn the user on ListenAndServe errors + newConnection := func(conn openvpn.IncomingConn) { + eventCh := make(chan openvpn.Event, 10) + log.Println("New connection into the management") + b.managementClient = conn.Open(eventCh) + b.managementClient.SetStateEvents(true) + b.eventHandler(eventCh) + } + log.Fatal(openvpn.ListenAndServe( + fmt.Sprintf("%s:%s", openvpnManagementAddr, openvpnManagementPort), + openvpn.IncomingConnHandlerFunc(newConnection), + )) +} + +func (b *Bitmask) eventHandler(eventCh <-chan openvpn.Event) { + // TODO: we are reporing only openvpn status, missing firewall status + for event := range eventCh { + log.Printf("Event: %v", event) + stateEvent, ok := event.(*openvpn.StateEvent) + if !ok { + continue + } + status, ok := statusNames[stateEvent.NewState()] + if ok { + b.statusCh <- status + } + } + b.statusCh <- Off +} + +func (b *Bitmask) getOpenvpnState() (string, error) { + if b.managementClient == nil { + return "", fmt.Errorf("No management connected") + } + stateEvent, err := b.managementClient.LatestState() + if err != nil { + return "", err + } + status, ok := statusNames[stateEvent.NewState()] + if !ok { + return "", fmt.Errorf("Unkonw status") + } + return status, nil +} diff --git a/bitmask_go/vpn.go b/bitmask_go/vpn.go new file mode 100644 index 0000000..09adcf0 --- /dev/null +++ b/bitmask_go/vpn.go @@ -0,0 +1,98 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "path" +) + +const ( + openvpnManagementAddr = "127.0.0.1" + openvpnManagementPort = "6061" +) + +var gateways = []string{ + "5.79.86.180", + "199.58.81.145", + "198.252.153.28", +} + +// StartVPN for provider +func (b *Bitmask) StartVPN(provider string) error { + // TODO: openvpn args are hardcoded + err := firewallStart(gateways) + if err != nil { + return err + } + + arg := []string{"--nobind", "--verb", "1"} + for _, gw := range gateways { + arg = append(arg, "--remote", gw, "443", "tcp4") + } + certPemPath := b.getCertPemPath() + arg = append(arg, "--client", "--tls-client", "--remote-cert-tls", "server", "--tls-cipher", "DHE-RSA-AES128-SHA", "--cipher", "AES-128-CBC", "--tun-ipv6", "--auth", "SHA1", "--keepalive", "10 30", "--management-client", "--management", openvpnManagementAddr+" "+openvpnManagementPort, "--ca", b.getCaCertPath(), "--cert", certPemPath, "--key", certPemPath) + return openvpnStart(arg...) +} + +// StopVPN or cancel +func (b *Bitmask) StopVPN() error { + err := firewallStop() + if err != nil { + return err + } + return openvpnStop() +} + +// GetStatus returns the VPN status +func (b *Bitmask) GetStatus() (string, error) { + status, err := b.getOpenvpnState() + if err != nil { + status = Off + } + return status, nil +} + +// InstallHelpers into the system +func (b *Bitmask) InstallHelpers() error { + // TODO + return nil +} + +// VPNCheck returns if the helpers are installed and up to date and if polkit is running +func (b *Bitmask) VPNCheck() (helpers bool, priviledge bool, err error) { + // TODO + return true, true, nil +} + +// ListGateways return the names of the gateways +func (b *Bitmask) ListGateways(provider string) ([]string, error) { + // TODO + return []string{}, nil +} + +// UseGateway selects name as the default gateway +func (b *Bitmask) UseGateway(name string) error { + // TODO + return nil +} + +func (b *Bitmask) getCertPemPath() string { + return path.Join(b.tempdir, "openvpn.pem") +} + +func (b *Bitmask) getCaCertPath() string { + return path.Join(b.tempdir, "cacert.pem") +} diff --git a/bitmaskd.go b/bitmaskd.go new file mode 100644 index 0000000..aa94ca0 --- /dev/null +++ b/bitmaskd.go @@ -0,0 +1,26 @@ +// +build !bitmask_go +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package main + +import ( + "0xacab.org/leap/bitmask-systray/bitmask" + bitmaskd "0xacab.org/leap/bitmask-systray/bitmaskd" +) + +func initBitmask() (bitmask.Bitmask, error) { + return bitmaskd.Init() +} diff --git a/bitmaskd/events.go b/bitmaskd/events.go new file mode 100644 index 0000000..e97804c --- /dev/null +++ b/bitmaskd/events.go @@ -0,0 +1,50 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "log" + "net/http" +) + +const ( + statusEvent = "VPN_STATUS_CHANGED" +) + +func (b *Bitmask) eventsHandler() { + b.send("events", "register", statusEvent) + client := &http.Client{ + Timeout: 0, + } + for { + resJSON, err := send(b.apiToken, client, "events", "poll") + res, ok := resJSON.([]interface{}) + if err != nil || !ok || len(res) < 1 { + continue + } + event, ok := res[0].(string) + if !ok || event != statusEvent { + continue + } + + status, err := b.GetStatus() + if err != nil { + log.Printf("Error receiving status: %v", err) + continue + } + b.statusCh <- status + } +} diff --git a/bitmaskd/main.go b/bitmaskd/main.go new file mode 100644 index 0000000..2ff2268 --- /dev/null +++ b/bitmaskd/main.go @@ -0,0 +1,163 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "log" + "net/http" + "path" + "time" + + "0xacab.org/leap/bitmask-systray/bitmask" +) + +const ( + timeout = time.Second * 15 + url = "http://localhost:7070/API/" + headerAuth = "X-Bitmask-Auth" +) + +// Bitmask holds the bitmask client data +type Bitmask struct { + client *http.Client + apiToken string + statusCh chan string +} + +// Init the connection to bitmask +func Init() (*Bitmask, error) { + statusCh := make(chan string) + client := &http.Client{ + Timeout: timeout, + } + + err := waitForBitmaskd() + if err != nil { + return nil, err + } + + apiToken, err := getToken() + if err != nil { + return nil, err + } + + b := Bitmask{client, apiToken, statusCh} + go b.eventsHandler() + return &b, nil +} + +// GetStatusCh returns a channel that will recieve VPN status changes +func (b *Bitmask) GetStatusCh() <-chan string { + return b.statusCh +} + +// Close the connection to bitmask +func (b *Bitmask) Close() { + _, err := b.send("core", "stop") + if err != nil { + log.Printf("Got an error stopping bitmaskd: %v", err) + } +} + +// Version gets the bitmask version string +func (b *Bitmask) Version() (string, error) { + res, err := b.send("core", "version") + if err != nil { + return "", err + } + return res["version_core"].(string), nil +} + +func waitForBitmaskd() error { + var err error + for i := 0; i < 30; i++ { + resp, err := http.Post(url, "", nil) + if err == nil { + resp.Body.Close() + return nil + } + log.Printf("Bitmask is not ready (iteration %d): %v", i, err) + time.Sleep(1 * time.Second) + } + return err +} + +func (b *Bitmask) send(parts ...interface{}) (map[string]interface{}, error) { + resJSON, err := send(b.apiToken, b.client, parts...) + if err != nil { + return nil, err + } + result, ok := resJSON.(map[string]interface{}) + if !ok { + return nil, errors.New("Not valid response") + } + return result, nil +} + +func send(apiToken string, client *http.Client, parts ...interface{}) (interface{}, error) { + apiSection, _ := parts[0].(string) + reqBody, err := json.Marshal(parts[1:]) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", url+apiSection, bytes.NewReader(reqBody)) + if err != nil { + return nil, err + } + req.Header.Add(headerAuth, apiToken) + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + resJSON, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return parseResponse(resJSON) +} + +func parseResponse(resJSON []byte) (interface{}, error) { + var response struct { + Result interface{} + Error string + } + err := json.Unmarshal(resJSON, &response) + if response.Error != "" { + return nil, errors.New(response.Error) + } + return response.Result, err +} + +func getToken() (string, error) { + var err error + path := path.Join(bitmask.ConfigPath, "authtoken") + for i := 0; i < 30; i++ { + b, err := ioutil.ReadFile(path) + if err == nil { + return string(b), nil + } + log.Printf("Auth token is not ready (iteration %d): %v", i, err) + time.Sleep(1 * time.Second) + } + return "", err +} diff --git a/bitmaskd/vpn.go b/bitmaskd/vpn.go new file mode 100644 index 0000000..aee5e8f --- /dev/null +++ b/bitmaskd/vpn.go @@ -0,0 +1,92 @@ +// Copyright (C) 2018 LEAP +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package bitmask + +import ( + "errors" + "log" +) + +// StartVPN for provider +func (b *Bitmask) StartVPN(provider string) error { + _, err := b.send("vpn", "start", provider) + return err +} + +// StopVPN or cancel +func (b *Bitmask) StopVPN() error { + _, err := b.send("vpn", "stop") + return err +} + +// GetStatus returns the VPN status +func (b *Bitmask) GetStatus() (string, error) { + res, err := b.send("vpn", "status") + if err != nil { + return "", err + } + return res["status"].(string), nil +} + +// InstallHelpers into the system +func (b *Bitmask) InstallHelpers() error { + _, err := b.send("vpn", "install") + return err +} + +// VPNCheck returns if the helpers are installed and up to date and if polkit is running +func (b *Bitmask) VPNCheck() (helpers bool, priviledge bool, err error) { + res, err := b.send("vpn", "check", "") + if err != nil { + return false, false, err + } + installed, ok := res["installed"].(bool) + if !ok { + log.Printf("Unexpected value for installed on 'vpn check': %v", res) + return false, false, errors.New("Invalid response format") + } + privcheck, ok := res["privcheck"].(bool) + if !ok { + log.Printf("Unexpected value for privcheck on 'vpn check': %v", res) + return installed, false, errors.New("Invalid response format") + } + return installed, privcheck, nil +} + +// ListGateways return the names of the gateways +func (b *Bitmask) ListGateways(provider string) ([]string, error) { + res, err := b.send("vpn", "list") + if err != nil { + return nil, err + } + + names := []string{} + locations, ok := res[provider].([]interface{}) + if !ok { + return nil, errors.New("Can't read the locations for provider " + provider) + } + for i := range locations { + loc := locations[i].(map[string]interface{}) + names = append(names, loc["name"].(string)) + } + return names, nil +} + +// UseGateway selects name as the default gateway +func (b *Bitmask) UseGateway(name string) error { + _, err := b.send("vpn", "locations", name) + return err +} diff --git a/main.go b/main.go index 5c8c74c..617bdf1 100644 --- a/main.go +++ b/main.go @@ -62,7 +62,7 @@ func main() { notify := newNotificator(conf) - b, err := bitmask.Init() + b, err := initBitmask() if err != nil { log.Print(err) notify.bitmaskNotRunning() @@ -74,7 +74,7 @@ func main() { run(b, conf, notify) } -func checkAndStartBitmask(b *bitmask.Bitmask, notify *notificator, conf *systrayConfig) { +func checkAndStartBitmask(b bitmask.Bitmask, notify *notificator, conf *systrayConfig) { err := checkAndInstallHelpers(b, notify) if err != nil { log.Printf("Is bitmask running? %v", err) @@ -87,7 +87,7 @@ func checkAndStartBitmask(b *bitmask.Bitmask, notify *notificator, conf *systray } } -func checkAndInstallHelpers(b *bitmask.Bitmask, notify *notificator) error { +func checkAndInstallHelpers(b bitmask.Bitmask, notify *notificator) error { helpers, priviledge, err := b.VPNCheck() if (err != nil && err.Error() == "nopolkit") || (err == nil && !priviledge) { log.Printf("No polkit found") @@ -106,7 +106,7 @@ func checkAndInstallHelpers(b *bitmask.Bitmask, notify *notificator) error { return nil } -func maybeStartVPN(b *bitmask.Bitmask, conf *systrayConfig) error { +func maybeStartVPN(b bitmask.Bitmask, conf *systrayConfig) error { if conf.UserStoppedVPN { return nil } diff --git a/systray.go b/systray.go index dd8a77c..aea8d04 100644 --- a/systray.go +++ b/systray.go @@ -27,7 +27,7 @@ import ( ) type bmTray struct { - bm *bitmask.Bitmask + bm bitmask.Bitmask conf *systrayConfig notify *notificator waitCh chan bool @@ -44,7 +44,7 @@ type gatewayTray struct { name string } -func run(bm *bitmask.Bitmask, conf *systrayConfig, notify *notificator) { +func run(bm bitmask.Bitmask, conf *systrayConfig, notify *notificator) { bt := bmTray{bm: bm, conf: conf, notify: notify} systray.Run(bt.onReady, bt.onExit) } @@ -117,13 +117,13 @@ func (bt *bmTray) onReady() { open.Run("https://riseup.net/vpn/donate") case <-mAbout.ClickedCh: bitmaskVersion, err := bt.bm.Version() + versionStr := version if err != nil { log.Printf("Error getting version: %v", err) - bt.notify.about(version) - } else { - versionStr := fmt.Sprintf("%s (bitmaskd %s)", version, bitmaskVersion) - bt.notify.about(versionStr) + } else if bitmaskVersion != "" { + versionStr = fmt.Sprintf("%s (bitmaskd %s)", version, bitmaskVersion) } + bt.notify.about(versionStr) case <-mQuit.ClickedCh: systray.Quit() -- cgit v1.2.3