2022-01-16 14:31:31 -05:00
|
|
|
package tbget
|
|
|
|
|
|
|
|
import (
|
2022-01-16 17:38:57 -05:00
|
|
|
"archive/tar"
|
2022-01-16 14:31:31 -05:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2022-01-16 21:22:04 -05:00
|
|
|
"os/exec"
|
2022-01-16 14:31:31 -05:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/cloudfoundry/jibber_jabber"
|
2022-01-16 17:38:57 -05:00
|
|
|
"github.com/ulikunitz/xz"
|
2022-01-16 15:33:47 -05:00
|
|
|
|
|
|
|
"github.com/jchavannes/go-pgp/pgp"
|
|
|
|
"golang.org/x/crypto/openpgp"
|
2022-01-16 14:31:31 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var wd, _ = os.Getwd()
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
var UNPACK_PATH = filepath.Join(wd, "unpack")
|
2022-01-16 14:31:31 -05:00
|
|
|
var DOWNLOAD_PATH = filepath.Join(wd, "tor-browser")
|
|
|
|
|
|
|
|
const TOR_UPDATES_URL string = "https://aus1.torproject.org/torbrowser/update_3/release/downloads.json"
|
|
|
|
|
|
|
|
var (
|
2022-01-16 21:22:04 -05:00
|
|
|
DefaultIETFLang, _ = jibber_jabber.DetectIETF()
|
2022-01-16 14:31:31 -05:00
|
|
|
)
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
type TBDownloader struct {
|
|
|
|
UnpackPath string
|
|
|
|
DownloadPath string
|
|
|
|
Lang string
|
|
|
|
OS, ARCH string
|
|
|
|
}
|
|
|
|
|
|
|
|
var OS = "linux"
|
|
|
|
var ARCH = "64"
|
|
|
|
|
|
|
|
func NewTBDownloader(lang string, os, arch string) *TBDownloader {
|
|
|
|
return &TBDownloader{
|
|
|
|
Lang: lang,
|
|
|
|
DownloadPath: DOWNLOAD_PATH,
|
|
|
|
UnpackPath: UNPACK_PATH,
|
|
|
|
OS: os,
|
|
|
|
ARCH: arch,
|
|
|
|
}
|
|
|
|
}
|
2022-01-16 21:22:04 -05:00
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) GetRuntimePair() string {
|
|
|
|
if t.OS != "" && t.ARCH != "" {
|
|
|
|
return fmt.Sprintf("%s%s", t.OS, t.ARCH)
|
2022-01-16 21:22:04 -05:00
|
|
|
}
|
2022-01-16 14:31:31 -05:00
|
|
|
switch runtime.GOOS {
|
|
|
|
case "darwin":
|
2022-01-21 22:47:18 -05:00
|
|
|
t.OS = "osx"
|
2022-01-16 14:31:31 -05:00
|
|
|
case "linux":
|
2022-01-21 22:47:18 -05:00
|
|
|
t.OS = "linux"
|
2022-01-16 14:31:31 -05:00
|
|
|
case "windows":
|
2022-01-21 22:47:18 -05:00
|
|
|
t.OS = "win"
|
2022-01-16 14:31:31 -05:00
|
|
|
default:
|
2022-01-21 22:47:18 -05:00
|
|
|
t.OS = "unknown"
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
switch runtime.GOARCH {
|
|
|
|
case "amd64":
|
2022-01-21 22:47:18 -05:00
|
|
|
t.ARCH = "64"
|
2022-01-16 14:31:31 -05:00
|
|
|
case "386":
|
2022-01-21 22:47:18 -05:00
|
|
|
t.ARCH = "32"
|
2022-01-16 14:31:31 -05:00
|
|
|
default:
|
2022-01-21 22:47:18 -05:00
|
|
|
t.ARCH = "unknown"
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
return fmt.Sprintf("%s%s", t.OS, t.ARCH)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) GetUpdater() (string, string, error) {
|
|
|
|
return t.GetUpdaterForLang(t.Lang)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) GetUpdaterForLang(ietf string) (string, string, error) {
|
2022-01-16 14:31:31 -05:00
|
|
|
jsonText, err := http.Get(TOR_UPDATES_URL)
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", "", fmt.Errorf("t.GetUpdaterForLang: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
defer jsonText.Body.Close()
|
2022-01-21 22:47:18 -05:00
|
|
|
return t.GetUpdaterForLangFromJson(jsonText.Body, ietf)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) GetUpdaterForLangFromJson(body io.ReadCloser, ietf string) (string, string, error) {
|
2022-01-16 14:31:31 -05:00
|
|
|
jsonBytes, err := io.ReadAll(body)
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", "", fmt.Errorf("t.GetUpdaterForLangFromJson: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
if err = ioutil.WriteFile(filepath.Join(t.DownloadPath, "downloads.json"), jsonBytes, 0644); err != nil {
|
|
|
|
return "", "", fmt.Errorf("t.GetUpdaterForLangFromJson: %s", err)
|
2022-01-16 17:06:29 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
return t.GetUpdaterForLangFromJsonBytes(jsonBytes, ietf)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) GetUpdaterForLangFromJsonBytes(jsonBytes []byte, ietf string) (string, string, error) {
|
2022-01-16 14:31:31 -05:00
|
|
|
var dat map[string]interface{}
|
|
|
|
if err := json.Unmarshal(jsonBytes, &dat); err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", "", fmt.Errorf("func (t *TBDownloader)Name: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
if platform, ok := dat["downloads"]; ok {
|
2022-01-21 22:47:18 -05:00
|
|
|
rtp := t.GetRuntimePair()
|
2022-01-16 14:31:31 -05:00
|
|
|
if updater, ok := platform.(map[string]interface{})[rtp]; ok {
|
|
|
|
if langUpdater, ok := updater.(map[string]interface{})[ietf]; ok {
|
|
|
|
return langUpdater.(map[string]interface{})["binary"].(string), langUpdater.(map[string]interface{})["sig"].(string), nil
|
|
|
|
}
|
|
|
|
// If we didn't find the language, try splitting at the hyphen
|
|
|
|
lang := strings.Split(ietf, "-")[0]
|
|
|
|
if langUpdater, ok := updater.(map[string]interface{})[lang]; ok {
|
|
|
|
return langUpdater.(map[string]interface{})["binary"].(string), langUpdater.(map[string]interface{})["sig"].(string), nil
|
|
|
|
}
|
|
|
|
// If we didn't find the language after splitting at the hyphen, try the default
|
2022-01-21 22:47:18 -05:00
|
|
|
return t.GetUpdaterForLangFromJsonBytes(jsonBytes, t.Lang)
|
2022-01-16 21:22:04 -05:00
|
|
|
} else {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", "", fmt.Errorf("t.GetUpdaterForLangFromJsonBytes: no updater for platform %s", rtp)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", "", fmt.Errorf("t.GetUpdaterForLangFromJsonBytes: %s", ietf)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) SingleFileDownload(url, name string) (string, error) {
|
|
|
|
path := filepath.Join(t.DownloadPath, name)
|
|
|
|
if !t.BotherToDownload(url, name) {
|
2022-01-16 16:04:26 -05:00
|
|
|
fmt.Printf("No updates required, skipping download of %s\n", name)
|
|
|
|
return path, nil
|
|
|
|
}
|
2022-01-16 14:31:31 -05:00
|
|
|
file, err := http.Get(url)
|
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", fmt.Errorf("SingleFileDownload: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
defer file.Body.Close()
|
2022-01-16 15:33:47 -05:00
|
|
|
outFile, err := os.Create(path)
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", fmt.Errorf("SingleFileDownload: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
defer outFile.Close()
|
|
|
|
io.Copy(outFile, file.Body)
|
|
|
|
return path, nil
|
|
|
|
}
|
|
|
|
|
2022-01-16 22:04:35 -05:00
|
|
|
func FileExists(path string) bool {
|
2022-01-16 16:07:40 -05:00
|
|
|
_, err := os.Stat(path)
|
|
|
|
return !os.IsNotExist(err)
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) BotherToDownload(url, name string) bool {
|
|
|
|
path := filepath.Join(t.DownloadPath, name)
|
2022-01-16 22:04:35 -05:00
|
|
|
if !FileExists(path) {
|
2022-01-16 16:07:40 -05:00
|
|
|
return true
|
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
defer ioutil.WriteFile(filepath.Join(t.DownloadPath, name+".last-url"), []byte(url), 0644)
|
|
|
|
lastUrl, err := ioutil.ReadFile(filepath.Join(t.DownloadPath, name+".last-url"))
|
2022-01-16 16:04:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if string(lastUrl) == url {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) NamePerPlatform(ietf string) string {
|
2022-01-16 21:22:04 -05:00
|
|
|
extension := "tar.xz"
|
|
|
|
windowsonly := ""
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "darwin":
|
|
|
|
extension = "dmg"
|
|
|
|
case "windows":
|
|
|
|
windowsonly = "-installer-"
|
|
|
|
extension = "exe"
|
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
return fmt.Sprintf("torbrowser%s-%s-%s.%s", windowsonly, t.GetRuntimePair(), ietf, extension)
|
2022-01-16 21:22:04 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) DownloadUpdater() (string, string, error) {
|
|
|
|
binary, sig, err := t.GetUpdater()
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", "", fmt.Errorf("DownloadUpdater: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
sigpath, err := t.SingleFileDownload(sig, t.NamePerPlatform(t.Lang)+".asc")
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", "", fmt.Errorf("DownloadUpdater: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
binpath, err := t.SingleFileDownload(binary, t.NamePerPlatform(t.Lang))
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", sigpath, fmt.Errorf("DownloadUpdater: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
return binpath, sigpath, nil
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) DownloadUpdaterForLang(ietf string) (string, string, error) {
|
|
|
|
binary, sig, err := t.GetUpdaterForLang(ietf)
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", "", fmt.Errorf("DownloadUpdaterForLang: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-16 21:22:04 -05:00
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
sigpath, err := t.SingleFileDownload(sig, t.NamePerPlatform(ietf)+".asc")
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", "", fmt.Errorf("DownloadUpdaterForLang: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
binpath, err := t.SingleFileDownload(binary, t.NamePerPlatform(ietf))
|
2022-01-16 14:31:31 -05:00
|
|
|
if err != nil {
|
2022-01-16 15:33:47 -05:00
|
|
|
return "", sigpath, fmt.Errorf("DownloadUpdaterForLang: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
return binpath, sigpath, nil
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) UnpackUpdater(binpath string) (string, error) {
|
|
|
|
if t.OS == "win" {
|
|
|
|
cmd := exec.Command("cmd", "/c", "start", "\""+t.UnpackPath+"\"", "\""+binpath+" /SD /D="+t.UnpackPath+"\"")
|
2022-01-16 21:22:04 -05:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: windows exec fail %s", err)
|
2022-01-16 21:22:04 -05:00
|
|
|
}
|
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
if t.OS == "osx" {
|
|
|
|
cmd := exec.Command("open", "-W", "-n", "-a", "\""+t.UnpackPath+"\"", "\""+binpath+"\"")
|
2022-01-16 21:22:04 -05:00
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: osx open/mount fail %s", err)
|
2022-01-16 21:22:04 -05:00
|
|
|
}
|
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
if FileExists(t.UnpackPath) {
|
|
|
|
return filepath.Join(t.UnpackPath, "tor-browser_"+t.Lang), nil
|
2022-01-16 21:22:04 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
os.MkdirAll(t.UnpackPath, 0755)
|
|
|
|
UNPACK_DIRECTORY, err := os.Open(t.UnpackPath)
|
2022-01-16 17:38:57 -05:00
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: %s", err)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
|
|
|
defer UNPACK_DIRECTORY.Close()
|
|
|
|
xzfile, err := os.Open(binpath)
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: %s", err)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
|
|
|
defer xzfile.Close()
|
|
|
|
xzReader, err := xz.NewReader(xzfile)
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: %s", err)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
|
|
|
tarReader := tar.NewReader(xzReader)
|
|
|
|
for {
|
|
|
|
header, err := tarReader.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: %s", err)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
|
|
|
if header.Typeflag == tar.TypeDir {
|
|
|
|
os.MkdirAll(filepath.Join(UNPACK_DIRECTORY.Name(), header.Name), 0755)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
filename := filepath.Join(UNPACK_DIRECTORY.Name(), header.Name)
|
|
|
|
file, err := os.Create(filename)
|
|
|
|
if err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("UnpackUpdater: %s", err)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
io.Copy(file, tarReader)
|
2022-01-16 22:04:35 -05:00
|
|
|
mode := header.FileInfo().Mode()
|
|
|
|
//remember to chmod the file afterwards
|
|
|
|
file.Chmod(mode)
|
2022-01-16 17:38:57 -05:00
|
|
|
}
|
2022-01-21 22:47:18 -05:00
|
|
|
return t.UnpackPath, nil
|
2022-01-16 17:38:57 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) CheckSignature(binpath, sigpath string) (string, error) {
|
2022-01-16 15:33:47 -05:00
|
|
|
var pkBytes []byte
|
|
|
|
var pk *openpgp.Entity
|
|
|
|
var sig []byte
|
2022-01-16 14:31:31 -05:00
|
|
|
var bin []byte
|
|
|
|
var err error
|
2022-01-21 22:47:18 -05:00
|
|
|
if pkBytes, err = ioutil.ReadFile(filepath.Join(t.DownloadPath, "TPO-signing-key.pub")); err != nil {
|
|
|
|
return "", fmt.Errorf("CheckSignature pkBytes: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-16 15:33:47 -05:00
|
|
|
if pk, err = pgp.GetEntity(pkBytes, nil); err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("CheckSignature pk: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-16 15:33:47 -05:00
|
|
|
if bin, err = ioutil.ReadFile(binpath); err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("CheckSignature bin: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-16 15:33:47 -05:00
|
|
|
if sig, err = ioutil.ReadFile(sigpath); err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("CheckSignature sig: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
2022-01-16 15:33:47 -05:00
|
|
|
if err = pgp.Verify(pk, sig, bin); err != nil {
|
2022-01-21 22:47:18 -05:00
|
|
|
return t.UnpackUpdater(binpath)
|
2022-01-16 17:38:57 -05:00
|
|
|
//return nil
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
err = fmt.Errorf("signature check failed")
|
2022-01-21 22:47:18 -05:00
|
|
|
return "", fmt.Errorf("CheckSignature: %s", err)
|
2022-01-16 14:31:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-21 22:47:18 -05:00
|
|
|
func (t *TBDownloader) BoolCheckSignature(binpath, sigpath string) bool {
|
|
|
|
_, err := t.CheckSignature(binpath, sigpath)
|
2022-01-16 14:31:31 -05:00
|
|
|
return err == nil
|
|
|
|
}
|