From 4fca210846b28caf1372edb5ec0abe8193d3ff8b Mon Sep 17 00:00:00 2001 From: "kali kaneko (leap communications)" Date: Wed, 24 Nov 2021 16:18:51 +0100 Subject: [feat] hook motd during bootstrap some refactor, plus fix docs --- branding/motd-cli/README.md | 6 +- branding/motd-cli/main.go | 123 +++--------------------------------- branding/motd-cli/motd-example.json | 15 ----- branding/motd-cli/motd_test.go | 83 ------------------------ pkg/backend/init.go | 6 +- pkg/backend/status.go | 4 ++ pkg/bitmask/bitmask.go | 3 + pkg/bitmask/init.go | 1 + pkg/config/version/checknewer.go | 6 +- pkg/motd/fetch.go | 54 ++++++++++++++++ pkg/motd/motd-example.json | 15 +++++ pkg/motd/motd.go | 118 ++++++++++++++++++++++++++++++++++ pkg/motd/motd_test.go | 83 ++++++++++++++++++++++++ pkg/vpn/main.go | 50 ++++++++++++++- 14 files changed, 345 insertions(+), 222 deletions(-) delete mode 100644 branding/motd-cli/motd-example.json delete mode 100644 branding/motd-cli/motd_test.go create mode 100644 pkg/motd/fetch.go create mode 100644 pkg/motd/motd-example.json create mode 100644 pkg/motd/motd.go create mode 100644 pkg/motd/motd_test.go diff --git a/branding/motd-cli/README.md b/branding/motd-cli/README.md index 5d00f10..5e66cee 100644 --- a/branding/motd-cli/README.md +++ b/branding/motd-cli/README.md @@ -15,8 +15,8 @@ The structure of the `motd.json` file is like follows: ``` { "motd": [{ - "begin": "Jan 1 2021 00:00:00", - "end": "Dec 31 2021 23:59:00", + "begin": "01 Nov 21 00:00 -0700", + "end": "31 Jan 22 00:00 -0700", "type": "daily", "platform": "all", "urgency": "normal", @@ -32,7 +32,7 @@ The structure of the `motd.json` file is like follows: Valid values are: -* Begin, End are date strings, like "Jan 1 2021 00:00:00". +* Begin, End are date strings, in the format: "01 Jan 21 00:00:00 -0700". * Type: "once" for a one-shot message, "daily" for a message that is displayed daily during the specified duration. * Platform: one of "windows", "osx", "snap", "linux", or "all". * Urgency: either "normal" or "critical". diff --git a/branding/motd-cli/main.go b/branding/motd-cli/main.go index 0ac1316..cea5910 100644 --- a/branding/motd-cli/main.go +++ b/branding/motd-cli/main.go @@ -1,110 +1,19 @@ package main import ( - "encoding/json" "flag" "fmt" "io" "io/ioutil" - "log" "net/http" "os" - "time" -) + "path/filepath" -/* TODO move structs to pkg/config/motd module, import from there */ + "0xacab.org/leap/bitmask-vpn/pkg/motd" +) -const defaultFile = "motd-example.json" const OK = "✓" const WRONG = "☓" -const TimeString = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone - -type Messages struct { - Messages []Message `json:"motd"` -} - -func (m *Messages) Length() int { - return len(m.Messages) -} - -type Message struct { - Begin string `json:"begin"` - End string `json:"end"` - Type string `json:"type"` - Platform string `json:"platform"` - Urgency string `json:"urgency"` - Text []LocalizedText `json:"text"` -} - -func (m *Message) IsValid() bool { - valid := (m.IsValidBegin() && m.IsValidEnd() && - m.IsValidType() && m.IsValidPlatform() && m.IsValidUrgency() && - m.HasLocalizedText()) - return valid -} - -func (m *Message) IsValidBegin() bool { - _, err := time.Parse(TimeString, m.Begin) - if err != nil { - log.Println(err) - return false - } - return true -} - -func (m *Message) IsValidEnd() bool { - endTime, err := time.Parse(TimeString, m.End) - if err != nil { - log.Println(err) - return false - } - beginTime, err := time.Parse(TimeString, m.Begin) - if err != nil { - log.Println(err) - return false - } - if !beginTime.Before(endTime) { - log.Println("begin ts should be before end") - return false - } - return true -} - -func (m *Message) IsValidType() bool { - switch m.Type { - case "once", "daily": - return true - default: - return false - } -} - -func (m *Message) IsValidPlatform() bool { - switch m.Platform { - case "windows", "linux", "osx", "all": - return true - default: - return false - } -} - -func (m *Message) IsValidUrgency() bool { - switch m.Urgency { - case "normal", "critical": - return true - default: - return false - } -} - -func (m *Message) HasLocalizedText() bool { - return len(m.Text) > 0 -} - -type LocalizedText struct { - Lang string `json:"lang"` - Str string `json:"str"` -} func main() { file := flag.String("file", "", "file to validate") @@ -114,16 +23,19 @@ func main() { f := *file u := *url + var m motd.Messages + var err error + if u != "" { fmt.Println("url:", u) f = downloadToTempFile(u) } else { if f == "" { - f = defaultFile + f = filepath.Join("../../pkg/motd/", motd.ExampleFile) } fmt.Println("file:", f) } - m, err := parseFile(f) + m, err = motd.ParseFile(f) if err != nil { panic(err) } @@ -159,25 +71,6 @@ func downloadToTempFile(url string) string { return out.Name() } -func parseFile(f string) (Messages, error) { - jsonFile, err := os.Open(f) - if err != nil { - panic(err) - } - defer jsonFile.Close() - byteVal, err := ioutil.ReadAll(jsonFile) - if err != nil { - panic(err) - } - return parseJsonStr(byteVal) -} - -func parseJsonStr(b []byte) (Messages, error) { - var m Messages - json.Unmarshal(b, &m) - return m, nil -} - func mark(val bool) string { if val { return OK diff --git a/branding/motd-cli/motd-example.json b/branding/motd-cli/motd-example.json deleted file mode 100644 index f33c2b9..0000000 --- a/branding/motd-cli/motd-example.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "motd": [{ - "begin": "02 Jan 21 15:04 -0700", - "end": "31 Jan 22 15:04 -0700", - "type": "daily", - "platform": "all", - "urgency": "normal", - "text": [ - { "lang": "en", - "str": "This is a test!"}, - { "lang": "es", - "str": "Esto es una pruebita!"} - ]} - ] -} diff --git a/branding/motd-cli/motd_test.go b/branding/motd-cli/motd_test.go deleted file mode 100644 index ed11661..0000000 --- a/branding/motd-cli/motd_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "testing" -) - -func TestGoodMotd(t *testing.T) { - m, err := parseFile(defaultFile) - if err != nil { - t.Errorf("error parsing default file") - } - if m.Length() == 0 { - t.Errorf("zero messages in file") - } - for _, msg := range m.Messages { - if !msg.IsValid() { - t.Errorf("invalid motd json at %s", defaultFile) - } - } -} - -const emptyDate = ` -{ - "motd": [{ - "begin": "", - "end": "", - "type": "daily", - "platform": "all", - "urgency": "normal", - "text": [ - { "lang": "en", - "str": "test" - }] - }] -}` - -func TestEmptyDateFails(t *testing.T) { - m, err := parseJsonStr([]byte(emptyDate)) - if err != nil { - t.Errorf("error parsing json") - } - if allValid(t, m) { - t.Errorf("empty string should not be valid") - } -} - -const badEnd = ` -{ - "motd": [{ - "begin": "02 Jan 21 00:00 +0100", - "end": "01 Jan 21 00:00 +0100", - "type": "daily", - "platform": "all", - "urgency": "normal", - "text": [ - { "lang": "en", - "str": "test" - }] - }] -}` - -func TestBadEnd(t *testing.T) { - m, err := parseJsonStr([]byte(badEnd)) - if err != nil { - t.Errorf("error parsing json") - } - if allValid(t, m) { - t.Errorf("begin > end must fail") - } -} - -func allValid(t *testing.T, m Messages) bool { - if m.Length() == 0 { - t.Errorf("expected at least one message") - - } - for _, msg := range m.Messages { - if !msg.IsValid() { - return false - } - } - return true -} diff --git a/pkg/backend/init.go b/pkg/backend/init.go index 70a3582..c6d713b 100644 --- a/pkg/backend/init.go +++ b/pkg/backend/init.go @@ -17,8 +17,10 @@ func initializeContext(opts *InitOpts) { var st status = off // TODO - now there's really no need to dance between opts and config anymore - // but this was the simplest transition. We should probably keep the multi-provider config in the backend too, and just - // switch the "active" here in the ctx, after the user has selected one in the combobox. + // but this was the simplest transition. We should probably keep the + // multi-provider config in the backend too, and just + // switch the "active" here in the ctx, after the user has selected one + // in the combobox. ctx = &connectionCtx{ AppName: opts.ProviderOptions.AppName, Provider: opts.ProviderOptions.Provider, diff --git a/pkg/backend/status.go b/pkg/backend/status.go index 79b70ff..f21fddd 100644 --- a/pkg/backend/status.go +++ b/pkg/backend/status.go @@ -54,6 +54,8 @@ type connectionCtx struct { UseUDP bool `json:"udp"` ManualLocation bool `json:"manualLocation"` IsReady bool `json:"isReady"` + CanUpgrade bool `json:"canUpgrade"` + Motd string `json:"motd"` bm bitmask.Bitmask autostart bitmask.Autostart cfg *config.Config @@ -72,6 +74,8 @@ func (c *connectionCtx) toJson() ([]byte, error) { c.Transport = transport c.UseUDP = c.cfg.UDP // TODO initialize bitmask too c.ManualLocation = c.bm.IsManualLocation() + c.CanUpgrade = c.bm.CanUpgrade() + c.Motd = c.bm.GetMotd() } defer statusMutex.Unlock() b, err := json.Marshal(c) diff --git a/pkg/bitmask/bitmask.go b/pkg/bitmask/bitmask.go index 5597efb..d02487b 100644 --- a/pkg/bitmask/bitmask.go +++ b/pkg/bitmask/bitmask.go @@ -32,6 +32,7 @@ type Bitmask interface { GetBestLocation(protocol string) string UseGateway(name string) UseAutomaticGateway() + SetProvider(string) GetTransport() string SetTransport(string) error UseUDP(bool) error @@ -41,4 +42,6 @@ type Bitmask interface { IsManualLocation() bool NeedsCredentials() bool DoLogin(username, password string) (bool, error) + CanUpgrade() bool + GetMotd() string } diff --git a/pkg/bitmask/init.go b/pkg/bitmask/init.go index ab40fed..bc7d47d 100644 --- a/pkg/bitmask/init.go +++ b/pkg/bitmask/init.go @@ -88,6 +88,7 @@ func InitializeBitmask(conf *config.Config) (Bitmask, error) { if err != nil { return nil, err } + b.SetProvider(config.Provider) err = setTransport(b, conf) if err != nil { diff --git a/pkg/config/version/checknewer.go b/pkg/config/version/checknewer.go index 4b89c34..115696a 100644 --- a/pkg/config/version/checknewer.go +++ b/pkg/config/version/checknewer.go @@ -48,9 +48,9 @@ func CanUpgrade() bool { if os.Getenv("DEBUG") == "1" { log.Println(">>> Remote version: " + r) log.Println(">>> Current version: " + VERSION) - if canUpgrade { - log.Println("Newer version available") - } + } + if canUpgrade { + log.Println("There's a newer version available:", r) } return canUpgrade } diff --git a/pkg/motd/fetch.go b/pkg/motd/fetch.go new file mode 100644 index 0000000..4f22388 --- /dev/null +++ b/pkg/motd/fetch.go @@ -0,0 +1,54 @@ +package motd + +import ( + "io/ioutil" + "log" + "net/http" + "os" + + "0xacab.org/leap/bitmask-vpn/pkg/config" +) + +func FetchLatest() []Message { + empty := []Message{} + if os.Getenv("SKIP_MOTD") == "1" { + return empty + } + url := "" + switch config.Provider { + case "riseup.net": + url = "https://downloads.leap.se/motd/riseup/motd.json" + default: + return empty + } + log.Println("Fetching MOTD for", config.Provider) + b, err := fetchURL(url) + if err != nil { + log.Println("WARN Error fetching json from", url) + return empty + } + allMsg, err := getFromJSON(b) + if err != nil { + log.Println("WARN Error parsing json from", url) + return empty + } + valid := empty[:] + if allMsg.Length() != 0 { + log.Printf("There are %d pending messages\n", allMsg.Length()) + } + for _, msg := range allMsg.Messages { + if msg.IsValid() { + valid = append(valid, msg) + } + } + return valid +} + +func fetchURL(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + return ioutil.ReadAll(resp.Body) +} diff --git a/pkg/motd/motd-example.json b/pkg/motd/motd-example.json new file mode 100644 index 0000000..20a7727 --- /dev/null +++ b/pkg/motd/motd-example.json @@ -0,0 +1,15 @@ +{ + "motd": [{ + "begin": "01 Nov 21 00:00 -0700", + "end": "31 Jan 22 00:00 -0700", + "type": "daily", + "platform": "all", + "urgency": "normal", + "text": [ + { "lang": "en", + "str": "Thanks for using RiseupVPN! Please report us any issue or feature request."}, + { "lang": "es", + "str": "¡Gracias por usar RiseupVPN! Por favor reportanos cualquier bug o petición."} + ]} + ] +} diff --git a/pkg/motd/motd.go b/pkg/motd/motd.go new file mode 100644 index 0000000..db21025 --- /dev/null +++ b/pkg/motd/motd.go @@ -0,0 +1,118 @@ +package motd + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "time" +) + +const TimeString = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone +const ExampleFile = "motd-example.json" + +func ParseFile(f string) (Messages, error) { + jsonFile, err := os.Open(f) + if err != nil { + panic(err) + } + defer jsonFile.Close() + byteVal, err := ioutil.ReadAll(jsonFile) + if err != nil { + panic(err) + } + return getFromJSON(byteVal) +} + +func getFromJSON(b []byte) (Messages, error) { + var m Messages + json.Unmarshal(b, &m) + return m, nil +} + +type Messages struct { + Messages []Message `json:"motd"` +} + +func (m *Messages) Length() int { + return len(m.Messages) +} + +type Message struct { + Begin string `json:"begin"` + End string `json:"end"` + Type string `json:"type"` + Platform string `json:"platform"` + Urgency string `json:"urgency"` + Text []LocalizedText `json:"text"` +} + +func (m *Message) IsValid() bool { + valid := (m.IsValidBegin() && m.IsValidEnd() && + m.IsValidType() && m.IsValidPlatform() && m.IsValidUrgency() && + m.HasLocalizedText()) + return valid +} + +func (m *Message) IsValidBegin() bool { + _, err := time.Parse(TimeString, m.Begin) + if err != nil { + log.Println(err) + return false + } + return true +} + +func (m *Message) IsValidEnd() bool { + endTime, err := time.Parse(TimeString, m.End) + if err != nil { + log.Println(err) + return false + } + beginTime, err := time.Parse(TimeString, m.Begin) + if err != nil { + log.Println(err) + return false + } + if !beginTime.Before(endTime) { + log.Println("begin ts should be before end") + return false + } + return true +} + +func (m *Message) IsValidType() bool { + switch m.Type { + case "once", "daily": + return true + default: + return false + } +} + +func (m *Message) IsValidPlatform() bool { + switch m.Platform { + case "windows", "linux", "osx", "all": + return true + default: + return false + } +} + +func (m *Message) IsValidUrgency() bool { + switch m.Urgency { + case "normal", "critical": + return true + default: + return false + } +} + +func (m *Message) HasLocalizedText() bool { + return len(m.Text) > 0 +} + +type LocalizedText struct { + Lang string `json:"lang"` + Str string `json:"str"` +} diff --git a/pkg/motd/motd_test.go b/pkg/motd/motd_test.go new file mode 100644 index 0000000..85a2f1c --- /dev/null +++ b/pkg/motd/motd_test.go @@ -0,0 +1,83 @@ +package motd + +import ( + "testing" +) + +func TestGoodMotd(t *testing.T) { + m, err := ParseFile(ExampleFile) + if err != nil { + t.Errorf("error parsing default file") + } + if m.Length() == 0 { + t.Errorf("zero messages in file") + } + for _, msg := range m.Messages { + if !msg.IsValid() { + t.Errorf("invalid motd json at %s", ExampleFile) + } + } +} + +const emptyDate = ` +{ + "motd": [{ + "begin": "", + "end": "", + "type": "daily", + "platform": "all", + "urgency": "normal", + "text": [ + { "lang": "en", + "str": "test" + }] + }] +}` + +func TestEmptyDateFails(t *testing.T) { + m, err := getFromJSON([]byte(emptyDate)) + if err != nil { + t.Errorf("error parsing json") + } + if allValid(t, m) { + t.Errorf("empty string should not be valid") + } +} + +const badEnd = ` +{ + "motd": [{ + "begin": "02 Jan 21 00:00 +0100", + "end": "01 Jan 21 00:00 +0100", + "type": "daily", + "platform": "all", + "urgency": "normal", + "text": [ + { "lang": "en", + "str": "test" + }] + }] +}` + +func TestBadEnd(t *testing.T) { + m, err := getFromJSON([]byte(badEnd)) + if err != nil { + t.Errorf("error parsing json") + } + if allValid(t, m) { + t.Errorf("begin > end must fail") + } +} + +func allValid(t *testing.T, m Messages) bool { + if m.Length() == 0 { + t.Errorf("expected at least one message") + + } + for _, msg := range m.Messages { + if !msg.IsValid() { + return false + } + } + return true +} diff --git a/pkg/vpn/main.go b/pkg/vpn/main.go index 0b1d316..619a10f 100644 --- a/pkg/vpn/main.go +++ b/pkg/vpn/main.go @@ -16,11 +16,14 @@ package vpn import ( + "encoding/json" "io/ioutil" "log" "os" "0xacab.org/leap/bitmask-vpn/pkg/config" + "0xacab.org/leap/bitmask-vpn/pkg/config/version" + "0xacab.org/leap/bitmask-vpn/pkg/motd" "0xacab.org/leap/bitmask-vpn/pkg/vpn/bonafide" "0xacab.org/leap/shapeshifter" "github.com/apparentlymart/go-openvpn-mgmt/openvpn" @@ -41,6 +44,9 @@ type Bitmask struct { openvpnArgs []string udp bool failed bool + canUpgrade bool + motd []motd.Message + provider string } // Init the connection to bitmask @@ -55,8 +61,18 @@ func Init() (*Bitmask, error) { if err != nil { return nil, err } - b := Bitmask{tempdir, bonafide.Gateway{}, bonafide.Gateway{}, statusCh, nil, bf, launch, "", nil, "", []string{}, false, false} + b := Bitmask{ + tempdir, + bonafide.Gateway{}, + bonafide.Gateway{}, statusCh, nil, bf, launch, + "", nil, "", []string{}, + false, false, false, + []motd.Message{}, ""} + // FIXME multiprovider: need to pass provider name early on + // XXX we want to block on these, but they can timeout if we're blocked. + b.checkForUpgrades() + b.checkForMOTD() b.launch.firewallStop() /* TODO -- we still want to do this, since it resets the fw/vpn if running @@ -77,6 +93,26 @@ func Init() (*Bitmask, error) { return &b, err } +func (b *Bitmask) SetProvider(p string) { + b.provider = p +} + +func (b *Bitmask) checkForUpgrades() { + + // SNAPS have their own way of upgrading. We probably should also try to detect + // if we've been installed via another package manager. + // For now, it's maybe a good idea to disable the UI check in linux, and be + // way more strict in windows/osx. + if os.Getenv("SNAP") != "" { + return + } + b.canUpgrade = version.CanUpgrade() +} + +func (b *Bitmask) checkForMOTD() { + b.motd = motd.FetchLatest() +} + // GetStatusCh returns a channel that will recieve VPN status changes func (b *Bitmask) GetStatusCh() <-chan string { return b.statusCh @@ -113,3 +149,15 @@ func (b *Bitmask) UseUDP(udp bool) error { b.udp = udp return nil } + +func (b *Bitmask) GetMotd() string { + bytes, err := json.Marshal(b.motd) + if err != nil { + log.Println("WARN error marshalling motd") + } + return string(bytes) +} + +func (b *Bitmask) CanUpgrade() bool { + return b.canUpgrade +} -- cgit v1.2.3