Files
i2p.plugins.tor-updater/get/get.go

296 lines
8.1 KiB
Go
Raw Normal View History

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