upgrade_supported.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build !noupgrade
  7. package upgrade
  8. import (
  9. "archive/tar"
  10. "archive/zip"
  11. "bytes"
  12. "compress/gzip"
  13. "crypto/tls"
  14. "encoding/json"
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "net/http"
  19. "os"
  20. "path"
  21. "path/filepath"
  22. "runtime"
  23. "sort"
  24. "strings"
  25. "github.com/syncthing/syncthing/lib/signature"
  26. )
  27. const DisabledByCompilation = false
  28. // This is an HTTP/HTTPS client that does *not* perform certificate
  29. // validation. We do this because some systems where Syncthing runs have
  30. // issues with old or missing CA roots. It doesn't actually matter that we
  31. // load the upgrade insecurely as we verify an ECDSA signature of the actual
  32. // binary contents before accepting the upgrade.
  33. var insecureHTTP = &http.Client{
  34. Transport: &http.Transport{
  35. TLSClientConfig: &tls.Config{
  36. InsecureSkipVerify: true,
  37. },
  38. },
  39. }
  40. // LatestGithubReleases returns the latest releases, including prereleases or
  41. // not depending on the argument
  42. func LatestGithubReleases(releasesURL, version string) ([]Release, error) {
  43. resp, err := insecureHTTP.Get(releasesURL)
  44. if err != nil {
  45. return nil, err
  46. }
  47. if resp.StatusCode > 299 {
  48. return nil, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
  49. }
  50. var rels []Release
  51. json.NewDecoder(resp.Body).Decode(&rels)
  52. resp.Body.Close()
  53. return rels, nil
  54. }
  55. type SortByRelease []Release
  56. func (s SortByRelease) Len() int {
  57. return len(s)
  58. }
  59. func (s SortByRelease) Swap(i, j int) {
  60. s[i], s[j] = s[j], s[i]
  61. }
  62. func (s SortByRelease) Less(i, j int) bool {
  63. return CompareVersions(s[i].Tag, s[j].Tag) > 0
  64. }
  65. func LatestRelease(releasesURL, version string) (Release, error) {
  66. rels, _ := LatestGithubReleases(releasesURL, version)
  67. return SelectLatestRelease(version, rels)
  68. }
  69. func SelectLatestRelease(version string, rels []Release) (Release, error) {
  70. if len(rels) == 0 {
  71. return Release{}, ErrVersionUnknown
  72. }
  73. sort.Sort(SortByRelease(rels))
  74. // Check for a beta build
  75. beta := strings.Contains(version, "-beta")
  76. for _, rel := range rels {
  77. if rel.Prerelease && !beta {
  78. continue
  79. }
  80. for _, asset := range rel.Assets {
  81. assetName := path.Base(asset.Name)
  82. // Check for the architecture
  83. expectedRelease := releaseName(rel.Tag)
  84. l.Debugf("expected release asset %q", expectedRelease)
  85. l.Debugln("considering release", assetName)
  86. if strings.HasPrefix(assetName, expectedRelease) {
  87. return rel, nil
  88. }
  89. }
  90. }
  91. return Release{}, ErrVersionUnknown
  92. }
  93. // Upgrade to the given release, saving the previous binary with a ".old" extension.
  94. func upgradeTo(binary string, rel Release) error {
  95. expectedRelease := releaseName(rel.Tag)
  96. l.Debugf("expected release asset %q", expectedRelease)
  97. for _, asset := range rel.Assets {
  98. assetName := path.Base(asset.Name)
  99. l.Debugln("considering release", assetName)
  100. if strings.HasPrefix(assetName, expectedRelease) {
  101. return upgradeToURL(binary, asset.URL)
  102. }
  103. }
  104. return ErrVersionUnknown
  105. }
  106. // Upgrade to the given release, saving the previous binary with a ".old" extension.
  107. func upgradeToURL(binary string, url string) error {
  108. fname, err := readRelease(filepath.Dir(binary), url)
  109. if err != nil {
  110. return err
  111. }
  112. old := binary + ".old"
  113. os.Remove(old)
  114. err = os.Rename(binary, old)
  115. if err != nil {
  116. return err
  117. }
  118. err = os.Rename(fname, binary)
  119. if err != nil {
  120. return err
  121. }
  122. return nil
  123. }
  124. func readRelease(dir, url string) (string, error) {
  125. l.Debugf("loading %q", url)
  126. req, err := http.NewRequest("GET", url, nil)
  127. if err != nil {
  128. return "", err
  129. }
  130. req.Header.Add("Accept", "application/octet-stream")
  131. resp, err := insecureHTTP.Do(req)
  132. if err != nil {
  133. return "", err
  134. }
  135. defer resp.Body.Close()
  136. switch runtime.GOOS {
  137. case "windows":
  138. return readZip(dir, resp.Body)
  139. default:
  140. return readTarGz(dir, resp.Body)
  141. }
  142. }
  143. func readTarGz(dir string, r io.Reader) (string, error) {
  144. gr, err := gzip.NewReader(r)
  145. if err != nil {
  146. return "", err
  147. }
  148. tr := tar.NewReader(gr)
  149. var tempName string
  150. var sig []byte
  151. // Iterate through the files in the archive.
  152. for {
  153. hdr, err := tr.Next()
  154. if err == io.EOF {
  155. // end of tar archive
  156. break
  157. }
  158. if err != nil {
  159. return "", err
  160. }
  161. shortName := path.Base(hdr.Name)
  162. l.Debugf("considering file %q", shortName)
  163. err = archiveFileVisitor(dir, &tempName, &sig, shortName, tr)
  164. if err != nil {
  165. return "", err
  166. }
  167. if tempName != "" && sig != nil {
  168. break
  169. }
  170. }
  171. if err := verifyUpgrade(tempName, sig); err != nil {
  172. return "", err
  173. }
  174. return tempName, nil
  175. }
  176. func readZip(dir string, r io.Reader) (string, error) {
  177. body, err := ioutil.ReadAll(r)
  178. if err != nil {
  179. return "", err
  180. }
  181. archive, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
  182. if err != nil {
  183. return "", err
  184. }
  185. var tempName string
  186. var sig []byte
  187. // Iterate through the files in the archive.
  188. for _, file := range archive.File {
  189. shortName := path.Base(file.Name)
  190. l.Debugf("considering file %q", shortName)
  191. inFile, err := file.Open()
  192. if err != nil {
  193. return "", err
  194. }
  195. err = archiveFileVisitor(dir, &tempName, &sig, shortName, inFile)
  196. inFile.Close()
  197. if err != nil {
  198. return "", err
  199. }
  200. if tempName != "" && sig != nil {
  201. break
  202. }
  203. }
  204. if err := verifyUpgrade(tempName, sig); err != nil {
  205. return "", err
  206. }
  207. return tempName, nil
  208. }
  209. // archiveFileVisitor is called for each file in an archive. It may set
  210. // tempFile and signature.
  211. func archiveFileVisitor(dir string, tempFile *string, signature *[]byte, filename string, filedata io.Reader) error {
  212. var err error
  213. switch filename {
  214. case "syncthing", "syncthing.exe":
  215. l.Debugln("reading binary")
  216. *tempFile, err = writeBinary(dir, filedata)
  217. if err != nil {
  218. return err
  219. }
  220. case "syncthing.sig", "syncthing.exe.sig":
  221. l.Debugln("reading signature")
  222. *signature, err = ioutil.ReadAll(filedata)
  223. if err != nil {
  224. return err
  225. }
  226. }
  227. return nil
  228. }
  229. func verifyUpgrade(tempName string, sig []byte) error {
  230. if tempName == "" {
  231. return fmt.Errorf("no upgrade found")
  232. }
  233. if sig == nil {
  234. return fmt.Errorf("no signature found")
  235. }
  236. l.Debugf("checking signature\n%s", sig)
  237. fd, err := os.Open(tempName)
  238. if err != nil {
  239. return err
  240. }
  241. err = signature.Verify(SigningKey, sig, fd)
  242. fd.Close()
  243. if err != nil {
  244. os.Remove(tempName)
  245. return err
  246. }
  247. return nil
  248. }
  249. func writeBinary(dir string, inFile io.Reader) (filename string, err error) {
  250. // Write the binary to a temporary file.
  251. outFile, err := ioutil.TempFile(dir, "syncthing")
  252. if err != nil {
  253. return "", err
  254. }
  255. _, err = io.Copy(outFile, inFile)
  256. if err != nil {
  257. os.Remove(outFile.Name())
  258. return "", err
  259. }
  260. err = outFile.Close()
  261. if err != nil {
  262. os.Remove(outFile.Name())
  263. return "", err
  264. }
  265. err = os.Chmod(outFile.Name(), os.FileMode(0755))
  266. if err != nil {
  267. os.Remove(outFile.Name())
  268. return "", err
  269. }
  270. return outFile.Name(), nil
  271. }