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 --- 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 ++++++++++++++++- 10 files changed, 334 insertions(+), 6 deletions(-) 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 (limited to 'pkg') 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