commit e5d543b50b146835783f27ccad5ca2beea6b6ac1 Author: Yawning Angel yawning@schwanenlied.me Date: Thu Dec 22 19:57:27 2016 +0000
Bug 20778: Refactor the update logic to prepare for background updates.
This refactors the update logic, and changes the config file flag signalling an update is required to a bool from a unix timestamp.
Note: This breaks the update functionality since updates are never triggered, but is required for the background check refactor. --- .../internal/ui/config/config.go | 22 +-- .../sandboxed-tor-browser/internal/ui/gtk/ui.go | 2 +- .../sandboxed-tor-browser/internal/ui/install.go | 155 +--------------- .../sandboxed-tor-browser/internal/ui/update.go | 205 +++++++++++++++++++++ 4 files changed, 214 insertions(+), 170 deletions(-)
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go index ff5ef9e..a1af1cd 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go @@ -25,7 +25,6 @@ import ( "os" "path/filepath" "runtime" - "time"
butils "git.schwanenlied.me/yawning/bulb.git/utils" xdg "github.com/cep21/xdgbasedir" @@ -300,9 +299,8 @@ type Config struct { // Locale is the Tor Browser locale to install ("en-US", "ja"). Locale string `json:"locale,omitempty"`
- // LastUpdateCheck is the UNIX time when the last update check was - // sucessfully completed. - LastUpdateCheck int64 `json:"lastUpdateCheck,omitEmpty"` + // ForceUpdate is set if the installed bundle is known to be obsolete. + ForceUpdate bool `json:"forceUpdate"`
// Tor is the Tor network configuration. Tor Tor `json:"tor,omitEmpty"` @@ -374,19 +372,11 @@ func (cfg *Config) SetFirstLaunch(b bool) { } }
-// NeedsUpdateCheck returns true if the bundle needs to be checked for updates, -// and possibly updated. -func (cfg *Config) NeedsUpdateCheck() bool { - const updateInterval = 60 * 60 * 12 // 12 hours. - now := time.Now().Unix() - return (now > cfg.LastUpdateCheck+updateInterval) || cfg.LastUpdateCheck > now -} - -// SetLastUpdateCheck sets the last update check time and marks the config +// SetForceUpdate sets the bundle as needed an update and marks the config // dirty. -func (cfg *Config) SetLastUpdateCheck(t int64) { - if cfg.LastUpdateCheck != t { - cfg.LastUpdateCheck = t +func (cfg *Config) SetForceUpdate(b bool) { + if cfg.ForceUpdate != b { + cfg.ForceUpdate = b cfg.isDirty = true } } diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go index 161b32f..4e526bb 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go @@ -159,7 +159,7 @@ func (ui *gtkUI) onDestroy() {
func (ui *gtkUI) launch() error { // If we don't need to update, and would just launch, quash the UI. - checkUpdate := ui.Cfg.NeedsUpdateCheck() + checkUpdate := ui.Cfg.ForceUpdate squelchUI := !checkUpdate && ui.Cfg.UseSystemTor
async := async.NewAsync() diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/install.go b/src/cmd/sandboxed-tor-browser/internal/ui/install.go index 0c9e80e..ac9f246 100644 --- a/src/cmd/sandboxed-tor-browser/internal/ui/install.go +++ b/src/cmd/sandboxed-tor-browser/internal/ui/install.go @@ -1,4 +1,4 @@ -// install.go - Install/Update logic. +// install.go - Install logic. // Copyright (C) 2016 Yawning Angel. // // This program is free software: you can redistribute it and/or modify @@ -17,20 +17,15 @@ package ui
import ( - "bytes" - "crypto/sha512" - "encoding/hex" "fmt" "io/ioutil" "log" "os" "path/filepath" "runtime" - "time"
"cmd/sandboxed-tor-browser/internal/data" "cmd/sandboxed-tor-browser/internal/installer" - "cmd/sandboxed-tor-browser/internal/sandbox" . "cmd/sandboxed-tor-browser/internal/ui/async" "cmd/sandboxed-tor-browser/internal/ui/config" "cmd/sandboxed-tor-browser/internal/utils" @@ -90,7 +85,6 @@ func (c *Common) DoInstall(async *Async) { return } } - checkAt := time.Now().Unix()
log.Printf("install: Version: %v Downloads: %v", version, downloads)
@@ -149,7 +143,7 @@ func (c *Common) DoInstall(async *Async) { }
// Set the appropriate bits in the config. - c.Cfg.SetLastUpdateCheck(checkAt) + c.Cfg.SetForceUpdate(false) c.Cfg.SetFirstLaunch(true)
// Sync the config, and return. @@ -173,148 +167,3 @@ func writeAutoconfig(cfg *config.Config) error {
return nil } - -func (c *Common) doUpdate(async *Async, dialFn dialFunc) { - // This attempts to follow the process that Firefox uses to check for - // updates. https://wiki.mozilla.org/Software_Update:Checking_For_Updates - - // Check for updates. - log.Printf("launch: Checking for updates.") - async.UpdateProgress("Checking for updates.") - - // Create the async HTTP client. - client := newHPKPGrabClient(dialFn) - - // Check the version, by downloading the XML file. - // XXX: Fall back to https over clearnet if the onion fails. - var update *installer.UpdateEntry - if url, err := installer.UpdateURL(c.Manif, true); err != nil { - async.Err = err - return - } else { - log.Printf("launch: Update URL: %v", url) - if b := async.Grab(client, url, nil); async.Err != nil { - return - } else if update, async.Err = installer.GetUpdateEntry(b); async.Err != nil { - return - } - } - - checkAt := time.Now().Unix() - if update == nil { - log.Printf("launch: Installed bundle is current.") - - // Save the time that the update check was done. - c.Cfg.SetLastUpdateCheck(checkAt) - async.Err = c.Cfg.Sync() - return - } - - // Force an update check again if the user exits for any reason, since - // we know there is an update available. - c.Cfg.SetLastUpdateCheck(0) - if async.Err = c.Cfg.Sync(); async.Err != nil { - return - } - - // Ensure that the update entry version is actually neweer. - if !c.Manif.BundleUpdateVersionValid(update.AppVersion) { - log.Printf("launch: Update server provided a downgrade: '%v'", update.AppVersion) - async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion) - return - } - - // Figure out the best MAR to download. - patches := make(map[string]*installer.Patch) - for _, v := range update.Patch { - if patches[v.Type] != nil { - async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type) - return - } - patches[v.Type] = &v - } - patch := patches["partial"] // Favor the delta update mechanism. - if patch == nil { - if patch = patches["complete"]; patch == nil { - async.Err = fmt.Errorf("no suitable MAR file found") - return - } - } - - // Download the MAR file. - log.Printf("update: Downloading %v", patch.Url) - async.UpdateProgress("Downloading Tor Browser Update.") - - var mar []byte - if mar = async.Grab(client, patch.Url, func(s string) { async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); async.Err != nil { - return - } - - log.Printf("update: Validating Tor Browser Update.") - async.UpdateProgress("Validating Tor Browser Update.") - - // Validate the hash against that listed in the XML file. - expectedHash, err := hex.DecodeString(patch.HashValue) - if err != nil { - async.Err = fmt.Errorf("failed to decode HashValue: %v", err) - return - } - switch patch.HashFunction { - case "SHA512": - derivedHash := sha512.Sum512(mar) - if !bytes.Equal(expectedHash, derivedHash[:]) { - async.Err = fmt.Errorf("downloaded hash does not match patch metadata") - return - } - default: - async.Err = fmt.Errorf("unsupported hash function: '%v'", patch.HashFunction) - return - } - - // ... and verify the signature block in the MAR with our copy of the key. - if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil { - return - } - - // Shutdown the old tor now. - if c.tor != nil { - log.Printf("update: Shutting down old tor.") - c.tor.Shutdown() - c.tor = nil - } - - // Apply the update. - log.Printf("update: Updating Tor Browser.") - async.UpdateProgress("Updating Tor Browser.") - - async.ToUI <- false // Lock out canceling. - - if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil { - return - } - - // Reinstall the autoconfig stuff. - if async.Err = writeAutoconfig(c.Cfg); async.Err != nil { - return - } - - // Update the maniftest and config. - c.Manif.SetVersion(update.AppVersion) - if async.Err = c.Manif.Sync(); async.Err != nil { - return - } - c.Cfg.SetLastUpdateCheck(checkAt) - if async.Err = c.Cfg.Sync(); async.Err != nil { - return - } - - async.ToUI <- true // Unlock canceling. - - // Restart tor if we launched it. - if !c.Cfg.UseSystemTor { - log.Printf("launch: Reconnecting to the Tor network.") - async.UpdateProgress("Reconnecting to the Tor network.") - _, async.Err = c.launchTor(async, false) - } - return -} diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/update.go b/src/cmd/sandboxed-tor-browser/internal/ui/update.go new file mode 100644 index 0000000..7c5f812 --- /dev/null +++ b/src/cmd/sandboxed-tor-browser/internal/ui/update.go @@ -0,0 +1,205 @@ +// update.go - Update logic. +// Copyright (C) 2016 Yawning Angel. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +package ui + +import ( + "bytes" + "crypto/sha512" + "encoding/hex" + "fmt" + "log" + + "cmd/sandboxed-tor-browser/internal/installer" + "cmd/sandboxed-tor-browser/internal/sandbox" + . "cmd/sandboxed-tor-browser/internal/ui/async" +) + +func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEntry { + // Check for updates. + log.Printf("update: Checking for updates.") + async.UpdateProgress("Checking for updates.") + + // Create the async HTTP client. + client := newHPKPGrabClient(dialFn) + + // Determine where the update metadata should be fetched from. + updateURLs := []string{} + for _, b := range []bool{true, false} { // Prioritize .onions. + if url, err := installer.UpdateURL(c.Manif, b); err != nil { + log.Printf("update: Failed to get update URL (onion: %v): %v", b, err) + } else { + updateURLs = append(updateURLs, url) + } + } + if len(updateURLs) == 0 { + log.Printf("update: Failed to find any update URLs") + async.Err = fmt.Errorf("failed to find any update URLs") + return nil + } + + // Check the version, by downloading the XML file. + var update *installer.UpdateEntry + fetchOk := false + for _, url := range updateURLs { + log.Printf("update: Metadata URL: %v", url) + async.Err = nil // Clear errors per fetch. + if b := async.Grab(client, url, nil); async.Err != nil { + log.Printf("update: Metadata download failed: %v", async.Err) + continue + } else if update, async.Err = installer.GetUpdateEntry(b); async.Err != nil { + log.Printf("update: Metadata parse failed: %v", async.Err) + continue + } + fetchOk = true + break + } + + if !fetchOk { + // This should be set from the last update attempt... + if async.Err == nil { + async.Err = fmt.Errorf("failed to download update metadata") + } + return nil + } + + if update == nil { + log.Printf("update: Installed bundle is current.") + c.Cfg.SetForceUpdate(false) + } else { + log.Printf("update: Installed bundle needs updating.") + c.Cfg.SetForceUpdate(true) + } + + if async.Err = c.Cfg.Sync(); async.Err != nil { + return nil + } + + return update +} + +func (c *Common) doUpdate(async *Async, dialFn dialFunc) { + // This attempts to follow the process that Firefox uses to check for + // updates. https://wiki.mozilla.org/Software_Update:Checking_For_Updates + + // Check for updates. + update := c.CheckUpdate(async, dialFn) + if async.Err != nil || update == nil { + return + } + + // Ensure that the update entry version is actually neweer. + if !c.Manif.BundleUpdateVersionValid(update.AppVersion) { + log.Printf("update: Update server provided a downgrade: '%v'", update.AppVersion) + async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion) + return + } + + // Figure out the best MAR to download. + patches := make(map[string]*installer.Patch) + for _, v := range update.Patch { + if patches[v.Type] != nil { + async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type) + return + } + patches[v.Type] = &v + } + patch := patches["partial"] // Favor the delta update mechanism. + if patch == nil { + if patch = patches["complete"]; patch == nil { + async.Err = fmt.Errorf("no suitable MAR file found") + return + } + } + + // Download the MAR file. + log.Printf("update: Downloading %v", patch.Url) + async.UpdateProgress("Downloading Tor Browser Update.") + + var mar []byte + client := newHPKPGrabClient(dialFn) + if mar = async.Grab(client, patch.Url, func(s string) { async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); async.Err != nil { + return + } + + log.Printf("update: Validating Tor Browser Update.") + async.UpdateProgress("Validating Tor Browser Update.") + + // Validate the hash against that listed in the XML file. + expectedHash, err := hex.DecodeString(patch.HashValue) + if err != nil { + async.Err = fmt.Errorf("failed to decode HashValue: %v", err) + return + } + switch patch.HashFunction { + case "SHA512": + derivedHash := sha512.Sum512(mar) + if !bytes.Equal(expectedHash, derivedHash[:]) { + async.Err = fmt.Errorf("downloaded hash does not match patch metadata") + return + } + default: + async.Err = fmt.Errorf("unsupported hash function: '%v'", patch.HashFunction) + return + } + + // ... and verify the signature block in the MAR with our copy of the key. + if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil { + return + } + + // Shutdown the old tor now. + if c.tor != nil { + log.Printf("update: Shutting down old tor.") + c.tor.Shutdown() + c.tor = nil + } + + // Apply the update. + log.Printf("update: Updating Tor Browser.") + async.UpdateProgress("Updating Tor Browser.") + + async.ToUI <- false // Lock out canceling. + + if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil { + return + } + + // Reinstall the autoconfig stuff. + if async.Err = writeAutoconfig(c.Cfg); async.Err != nil { + return + } + + // Update the maniftest and config. + c.Manif.SetVersion(update.AppVersion) + if async.Err = c.Manif.Sync(); async.Err != nil { + return + } + c.Cfg.SetForceUpdate(false) + if async.Err = c.Cfg.Sync(); async.Err != nil { + return + } + + async.ToUI <- true // Unlock canceling. + + // Restart tor if we launched it. + if !c.Cfg.UseSystemTor { + log.Printf("launch: Reconnecting to the Tor network.") + async.UpdateProgress("Reconnecting to the Tor network.") + _, async.Err = c.launchTor(async, false) + } + return +}
tor-commits@lists.torproject.org