From a780e4a5c77ecdbc2c6280e4ea71ac60130d72d7 Mon Sep 17 00:00:00 2001 From: Roman Zakharchuk Date: Mon, 10 Mar 2025 04:58:37 +0200 Subject: [PATCH] restore update feature (#36) * replace wg.exe with awg.exe * update docs, update admin reg key * rename awg modules * build awg from source * remove unused params * improve work with tools * safe rebranding: change upgrade code, windows class and name * safe rebranding: wg -> awg * update dependencies, fixed showing transfered KBs * Revert "remove application update feature" This reverts commit 9670f4298ea8f4f116552d0182852ed35d7cf78b. * Revert "remove application update feature #2" This reverts commit b0c96a9fd6124331400740468a3d14a18f354561. * updatepage and tray change WireGuard to AmneziaWG * move crypto from indirect to direct --------- Signed-off-by: Roman Zakharchuk --- go.mod | 2 +- locales/ca/messages.gotext.json | 6 +- locales/cs/messages.gotext.json | 18 +- locales/de/messages.gotext.json | 18 +- locales/en/messages.gotext.json | 18 +- locales/es-ES/messages.gotext.json | 12 +- locales/fa/messages.gotext.json | 6 +- locales/fi/messages.gotext.json | 12 +- locales/fr/messages.gotext.json | 18 +- locales/it/messages.gotext.json | 18 +- locales/ja/messages.gotext.json | 18 +- locales/pa-IN/messages.gotext.json | 14 +- locales/pl/messages.gotext.json | 18 +- locales/ro/messages.gotext.json | 18 +- locales/ru/messages.gotext.json | 18 +- locales/sk/messages.gotext.json | 18 +- locales/sl/messages.gotext.json | 18 +- locales/tr/messages.gotext.json | 18 +- locales/zh-CN/messages.gotext.json | 18 +- locales/zh-TW/messages.gotext.json | 18 +- main.go | 26 ++ manager/ipc_client.go | 97 ++++++++ manager/ipc_server.go | 37 +++ manager/service.go | 2 + manager/updatestate.go | 65 +++++ ui/managewindow.go | 15 ++ ui/tray.go | 33 +++ ui/ui.go | 24 ++ ui/updatepage.go | 142 +++++++++++ updater/authenticode.go | 34 +++ updater/constants.go | 17 ++ updater/downloader.go | 200 ++++++++++++++++ updater/msirunner.go | 116 +++++++++ updater/signify.go | 72 ++++++ updater/updater_test.go | 41 ++++ updater/versions.go | 75 ++++++ updater/winhttp/mksyscall.go | 8 + updater/winhttp/syscall_windows.go | 355 ++++++++++++++++++++++++++++ updater/winhttp/winhttp.go | 230 ++++++++++++++++++ updater/winhttp/winhttp_test.go | 71 ++++++ updater/winhttp/zsyscall_windows.go | 155 ++++++++++++ 41 files changed, 1967 insertions(+), 152 deletions(-) create mode 100644 manager/updatestate.go create mode 100644 ui/updatepage.go create mode 100644 updater/authenticode.go create mode 100644 updater/constants.go create mode 100644 updater/downloader.go create mode 100644 updater/msirunner.go create mode 100644 updater/signify.go create mode 100644 updater/updater_test.go create mode 100644 updater/versions.go create mode 100644 updater/winhttp/mksyscall.go create mode 100644 updater/winhttp/syscall_windows.go create mode 100644 updater/winhttp/winhttp.go create mode 100644 updater/winhttp/winhttp_test.go create mode 100644 updater/winhttp/zsyscall_windows.go diff --git a/go.mod b/go.mod index 4daea53..05bab05 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/amnezia-vpn/amneziawg-windows v0.1.4-0.20240526104134-db18f2297e5e github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 github.com/lxn/win v0.0.0-20210218163916-a377121e959e + golang.org/x/crypto v0.21.0 golang.org/x/sys v0.18.0 golang.org/x/text v0.14.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 @@ -14,7 +15,6 @@ require ( require ( github.com/tevino/abool/v2 v2.1.0 // indirect - golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/tools v0.6.0 // indirect diff --git a/locales/ca/messages.gotext.json b/locales/ca/messages.gotext.json index 6764181..9cd7718 100644 --- a/locales/ca/messages.gotext.json +++ b/locales/ca/messages.gotext.json @@ -714,9 +714,9 @@ "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Una actualització per WireGuard està disponible. Es recomana actualitzar immediatament.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Una actualització per AmneziaWG està disponible. Es recomana actualitzar immediatament.", "translatorComment": "Copied from source." }, { diff --git a/locales/cs/messages.gotext.json b/locales/cs/messages.gotext.json index e07806e..a5a5376 100644 --- a/locales/cs/messages.gotext.json +++ b/locales/cs/messages.gotext.json @@ -1308,15 +1308,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Aktualizace WireGuard je k dispozici", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Aktualizace AmneziaWG je k dispozici", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Aktualizace aplikace WireGuard je nyní k dispozici. Doporučujeme ji aktualizovat co nejdříve.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Aktualizace aplikace AmneziaWG je nyní k dispozici. Doporučujeme ji aktualizovat co nejdříve.", "translatorComment": "Copied from source." }, { @@ -1778,9 +1778,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Aktualizace aplikace WireGuard je nyní k dispozici. Silně doporučujeme ji aktualizovat co nejdříve.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Aktualizace aplikace AmneziaWG je nyní k dispozici. Silně doporučujeme ji aktualizovat co nejdříve.", "translatorComment": "Copied from source." }, { diff --git a/locales/de/messages.gotext.json b/locales/de/messages.gotext.json index e17602c..a00413e 100644 --- a/locales/de/messages.gotext.json +++ b/locales/de/messages.gotext.json @@ -1278,15 +1278,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard Aktualisierung verfügbar", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG Aktualisierung verfügbar", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Eine Aktualisierung für WireGuard ist jetzt verfügbar. Es wird empfohlen diese schnellstmöglich durchzuführen.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Eine Aktualisierung für AmneziaWG ist jetzt verfügbar. Es wird empfohlen diese schnellstmöglich durchzuführen.", "translatorComment": "Copied from source." }, { @@ -1718,9 +1718,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Eine Aktualisierung für WireGuard ist verfügbar. Es ist höchst empfehlenswert diese sofort durchzuführen.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Eine Aktualisierung für AmneziaWG ist verfügbar. Es ist höchst empfehlenswert diese sofort durchzuführen.", "translatorComment": "Copied from source." }, { diff --git a/locales/en/messages.gotext.json b/locales/en/messages.gotext.json index 50620cf..0b071c3 100644 --- a/locales/en/messages.gotext.json +++ b/locales/en/messages.gotext.json @@ -1351,16 +1351,16 @@ "fuzzy": true }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard Update Available", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG Update Available", "translatorComment": "Copied from source.", "fuzzy": true }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "An update to WireGuard is now available. You are advised to update as soon as possible.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", "translatorComment": "Copied from source.", "fuzzy": true }, @@ -1830,9 +1830,9 @@ "fuzzy": true }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "An update to WireGuard is available. It is highly advisable to update without delay.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "An update to AmneziaWG is available. It is highly advisable to update without delay.", "translatorComment": "Copied from source.", "fuzzy": true }, diff --git a/locales/es-ES/messages.gotext.json b/locales/es-ES/messages.gotext.json index 6bee04e..62fe43c 100644 --- a/locales/es-ES/messages.gotext.json +++ b/locales/es-ES/messages.gotext.json @@ -758,15 +758,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Actualización de WireGuard disponible", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Actualización de AmneziaWG disponible", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Ya está disponible una actualización de WireGuard. Se recomienda actualizar lo antes posible.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Ya está disponible una actualización de AmneziaWG. Se recomienda actualizar lo antes posible.", "translatorComment": "Copied from source." }, { diff --git a/locales/fa/messages.gotext.json b/locales/fa/messages.gotext.json index 96f3d21..b0212a6 100644 --- a/locales/fa/messages.gotext.json +++ b/locales/fa/messages.gotext.json @@ -594,9 +594,9 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "به‌روزرسانی WireGuard در دسترس است", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "به‌روزرسانی AmneziaWG در دسترس است", "translatorComment": "Copied from source." }, { diff --git a/locales/fi/messages.gotext.json b/locales/fi/messages.gotext.json index ea7efed..0daee4c 100644 --- a/locales/fi/messages.gotext.json +++ b/locales/fi/messages.gotext.json @@ -342,15 +342,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard päivitys saatavilla", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG päivitys saatavilla", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "WireGuardin päivitys on nyt saatavilla. Sinua kehotetaan päivittämään mahdollisimman pian.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "AmneziaWGin päivitys on nyt saatavilla. Sinua kehotetaan päivittämään mahdollisimman pian.", "translatorComment": "Copied from source." }, { diff --git a/locales/fr/messages.gotext.json b/locales/fr/messages.gotext.json index ccbb9e7..27a2c86 100644 --- a/locales/fr/messages.gotext.json +++ b/locales/fr/messages.gotext.json @@ -1278,15 +1278,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard mise à jour est disponible", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG mise à jour est disponible", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Une mise à jour du WireGuard est disponible. Il est conseillé de mettre votre WireGuard à jour dès que possible.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Une mise à jour du AmneziaWG est disponible. Il est conseillé de mettre votre AmneziaWG à jour dès que possible.", "translatorComment": "Copied from source." }, { @@ -1718,9 +1718,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Une mise à jour du WireGuard est disponible. Il est fortement conseillé de metter votre WireGuard à jour sans délai.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Une mise à jour du AmneziaWG est disponible. Il est fortement conseillé de metter votre AmneziaWG à jour sans délai.", "translatorComment": "Copied from source." }, { diff --git a/locales/it/messages.gotext.json b/locales/it/messages.gotext.json index 677b19a..80f89b3 100644 --- a/locales/it/messages.gotext.json +++ b/locales/it/messages.gotext.json @@ -1278,15 +1278,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Aggiornamento di WireGuard disponibile", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Aggiornamento di AmneziaWG disponibile", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Un aggiornamento di WireGuard è disponibile. Ti consigliamo di aggiornare il prima possibile.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Un aggiornamento di AmneziaWG è disponibile. Ti consigliamo di aggiornare il prima possibile.", "translatorComment": "Copied from source." }, { @@ -1718,9 +1718,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Un aggiornamento di WireGuard è disponibile. Ti consigliamo vivamente di aggiornare immediatamente.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Un aggiornamento di AmneziaWG è disponibile. Ti consigliamo vivamente di aggiornare immediatamente.", "translatorComment": "Copied from source." }, { diff --git a/locales/ja/messages.gotext.json b/locales/ja/messages.gotext.json index ba725d7..d2acd2e 100644 --- a/locales/ja/messages.gotext.json +++ b/locales/ja/messages.gotext.json @@ -1263,15 +1263,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard の更新が利用可能です", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG の更新が利用可能です", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "WireGuard の更新が利用可能になりました。できるだけ早く更新してください。", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "AmneziaWG の更新が利用可能になりました。できるだけ早く更新してください。", "translatorComment": "Copied from source." }, { @@ -1688,9 +1688,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "WireGuard の更新が利用可能です。速やかに更新することを強く推奨します。", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "AmneziaWG の更新が利用可能です。速やかに更新することを強く推奨します。", "translatorComment": "Copied from source." }, { diff --git a/locales/pa-IN/messages.gotext.json b/locales/pa-IN/messages.gotext.json index ace02f1..4b1dd78 100644 --- a/locales/pa-IN/messages.gotext.json +++ b/locales/pa-IN/messages.gotext.json @@ -1096,14 +1096,14 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", "translation": "ਵਾਇਰਗਾਰਡ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", "translation": "ਵਾਇਰਗਾਰਡ ਲਈ ਅੱਪਡੇਟ ਹੁਣ ਮੌਜੂਦ ਹੈ। ਜਿੰਨਾ ਛੇਤੀ ਹੋ ਸਕੇ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।", "translatorComment": "Copied from source." }, @@ -1418,9 +1418,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "WireGuard ਲਈ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ। ਤੁਹਾਨੂੰ ਬਿਨਾਂ ਦੇਰ ਕੀਤਿਆਂ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "AmneziaWG ਲਈ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ। ਤੁਹਾਨੂੰ ਬਿਨਾਂ ਦੇਰ ਕੀਤਿਆਂ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਲਾਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ।", "translatorComment": "Copied from source." }, { diff --git a/locales/pl/messages.gotext.json b/locales/pl/messages.gotext.json index 8ceeca5..28e2362 100644 --- a/locales/pl/messages.gotext.json +++ b/locales/pl/messages.gotext.json @@ -1308,15 +1308,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Aktualizacja WireGuard jest dostępna", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Aktualizacja AmneziaWG jest dostępna", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Aktualizacja WireGuard jest już dostępna. Zaleca się jak najszybszą aktualizację.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Aktualizacja AmneziaWG jest już dostępna. Zaleca się jak najszybszą aktualizację.", "translatorComment": "Copied from source." }, { @@ -1778,9 +1778,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Aktualizacja WireGuard jest dostępna. Zaleca się natychmiastową aktualizację.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Aktualizacja AmneziaWG jest dostępna. Zaleca się natychmiastową aktualizację.", "translatorComment": "Copied from source." }, { diff --git a/locales/ro/messages.gotext.json b/locales/ro/messages.gotext.json index 2cd99bd..821d1dc 100644 --- a/locales/ro/messages.gotext.json +++ b/locales/ro/messages.gotext.json @@ -1293,15 +1293,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Actualizare disponibilă pentru WireGuard", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Actualizare disponibilă pentru AmneziaWG", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "O actualizare pentru WireGuard este acum disponibilă. Se recomandă efectuarea actualizării cât mai rapid posibil.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "O actualizare pentru AmneziaWG este acum disponibilă. Se recomandă efectuarea actualizării cât mai rapid posibil.", "translatorComment": "Copied from source." }, { @@ -1748,9 +1748,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Este disponibilă o actualizare pentru WireGuard. Se recomandă ferm actualizarea imediată.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Este disponibilă o actualizare pentru AmneziaWG. Se recomandă ferm actualizarea imediată.", "translatorComment": "Copied from source." }, { diff --git a/locales/ru/messages.gotext.json b/locales/ru/messages.gotext.json index bc454ee..5c1da6d 100644 --- a/locales/ru/messages.gotext.json +++ b/locales/ru/messages.gotext.json @@ -1308,15 +1308,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Доступно обновление WireGuard", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Доступно обновление AmneziaWG", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Доступно обновление для WireGuard. Рекомендуется обновить его как можно скорее.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Доступно обновление для AmneziaWG. Рекомендуется обновить его как можно скорее.", "translatorComment": "Copied from source." }, { @@ -1788,9 +1788,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Доступно обновление WireGuard. Настоятельно рекомендуем обновить приложение.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Доступно обновление AmneziaWG. Настоятельно рекомендуем обновить приложение.", "translatorComment": "Copied from source." }, { diff --git a/locales/sk/messages.gotext.json b/locales/sk/messages.gotext.json index 00f12cd..407508f 100644 --- a/locales/sk/messages.gotext.json +++ b/locales/sk/messages.gotext.json @@ -1308,15 +1308,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Dostupná aktualizácia pre WireGuard", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Dostupná aktualizácia pre AmneziaWG", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Je k dispozícii aktualizácia programu WireGuard. Je odporúčané čo najskôr vykonať aktualizáciu.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Je k dispozícii aktualizácia programu AmneziaWG. Je odporúčané čo najskôr vykonať aktualizáciu.", "translatorComment": "Copied from source." }, { @@ -1778,9 +1778,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Je k dispozícii nová verzia programu WireGuard. Odporúčame bezodkladne vykonať aktualizáciu.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Je k dispozícii nová verzia programu AmneziaWG. Odporúčame bezodkladne vykonať aktualizáciu.", "translatorComment": "Copied from source." }, { diff --git a/locales/sl/messages.gotext.json b/locales/sl/messages.gotext.json index 581d36f..f3a584f 100644 --- a/locales/sl/messages.gotext.json +++ b/locales/sl/messages.gotext.json @@ -1308,15 +1308,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "Posodobitev WireGuarda je na voljo", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "Posodobitev AmneziaWGa je na voljo", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "Posodobitev WireGuarda je na voljo. Svetujemo posodobitev čim prej.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "Posodobitev AmneziaWGa je na voljo. Svetujemo posodobitev čim prej.", "translatorComment": "Copied from source." }, { @@ -1778,9 +1778,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "Posodobitev WireGuarda je na voljo. Zelo priporočamo posodobitev brez odlašanja.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "Posodobitev AmneziaWG je na voljo. Zelo priporočamo posodobitev brez odlašanja.", "translatorComment": "Copied from source." }, { diff --git a/locales/tr/messages.gotext.json b/locales/tr/messages.gotext.json index aa7391a..ed18e33 100644 --- a/locales/tr/messages.gotext.json +++ b/locales/tr/messages.gotext.json @@ -1278,15 +1278,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard Güncellemesi Mevcut", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG Güncellemesi Mevcut", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "WireGuard için bir güncelleme mevcut. İlk fırsatta güncelleme yapmanız tavsiye edilir.", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "AmneziaWG için bir güncelleme mevcut. İlk fırsatta güncelleme yapmanız tavsiye edilir.", "translatorComment": "Copied from source." }, { @@ -1718,9 +1718,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "WireGuard için bir güncelleme mevcut. Bekletmeden güncelleme yapmanız önemle tavsiye edilir.", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "AmneziaWG için bir güncelleme mevcut. Bekletmeden güncelleme yapmanız önemle tavsiye edilir.", "translatorComment": "Copied from source." }, { diff --git a/locales/zh-CN/messages.gotext.json b/locales/zh-CN/messages.gotext.json index e2215d1..b421711 100644 --- a/locales/zh-CN/messages.gotext.json +++ b/locales/zh-CN/messages.gotext.json @@ -1263,15 +1263,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard 更新", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG 更新", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "新的 WireGuard 版本发布了。强烈建议您现在安装。", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "新的 AmneziaWG 版本发布了。强烈建议您现在安装。", "translatorComment": "Copied from source." }, { @@ -1688,9 +1688,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "发现新版 WireGuard。强烈建议您现在安装。", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "发现新版 AmneziaWG。强烈建议您现在安装。", "translatorComment": "Copied from source." }, { diff --git a/locales/zh-TW/messages.gotext.json b/locales/zh-TW/messages.gotext.json index 4fa8e98..6bbca10 100644 --- a/locales/zh-TW/messages.gotext.json +++ b/locales/zh-TW/messages.gotext.json @@ -1251,15 +1251,15 @@ "translatorComment": "Copied from source." }, { - "id": "WireGuard Update Available", - "message": "WireGuard Update Available", - "translation": "WireGuard 更新", + "id": "AmneziaWG Update Available", + "message": "AmneziaWG Update Available", + "translation": "AmneziaWG 更新", "translatorComment": "Copied from source." }, { - "id": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "message": "An update to WireGuard is now available. You are advised to update as soon as possible.", - "translation": "更新的 WireGuard 已經為您準備好了。\n強烈建議您立即更新 WireGuard。", + "id": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "message": "An update to AmneziaWG is now available. You are advised to update as soon as possible.", + "translation": "更新的 AmneziaWG 已經為您準備好了。\n強烈建議您立即更新 AmneziaWG。", "translatorComment": "Copied from source." }, { @@ -1676,9 +1676,9 @@ ] }, { - "id": "An update to WireGuard is available. It is highly advisable to update without delay.", - "message": "An update to WireGuard is available. It is highly advisable to update without delay.", - "translation": "更新的 WireGuard 已經為您準備好了。\n強烈建議您立即進行更新。", + "id": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "message": "An update to AmneziaWG is available. It is highly advisable to update without delay.", + "translation": "更新的 AmneziaWG 已經為您準備好了。\n強烈建議您立即進行更新。", "translatorComment": "Copied from source." }, { diff --git a/main.go b/main.go index fb0817e..6befe44 100755 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "github.com/amnezia-vpn/amneziawg-windows-client/manager" "github.com/amnezia-vpn/amneziawg-windows-client/ringlogger" "github.com/amnezia-vpn/amneziawg-windows-client/ui" + "github.com/amnezia-vpn/amneziawg-windows-client/updater" ) func setLogFile() { @@ -71,6 +72,7 @@ func usage() { "/tunnelservice CONFIG_PATH", "/ui CMD_READ_HANDLE CMD_WRITE_HANDLE CMD_EVENT_HANDLE LOG_MAPPING_HANDLE", "/dumplog [/tail]", + "/update", } builder := strings.Builder{} for _, flag := range flags { @@ -286,6 +288,30 @@ func main() { fatal(err) } return + case "/update": + if len(os.Args) != 2 { + usage() + } + for progress := range updater.DownloadVerifyAndExecute(0) { + if len(progress.Activity) > 0 { + if progress.BytesTotal > 0 || progress.BytesDownloaded > 0 { + var percent float64 + if progress.BytesTotal > 0 { + percent = float64(progress.BytesDownloaded) / float64(progress.BytesTotal) * 100.0 + } + log.Printf("%s: %d/%d (%.2f%%)\n", progress.Activity, progress.BytesDownloaded, progress.BytesTotal, percent) + } else { + log.Println(progress.Activity) + } + } + if progress.Error != nil { + log.Printf("Error: %v\n", progress.Error) + } + if progress.Complete || progress.Error != nil { + return + } + } + return } usage() } diff --git a/manager/ipc_client.go b/manager/ipc_client.go index 0597575..eaeda81 100644 --- a/manager/ipc_client.go +++ b/manager/ipc_client.go @@ -11,6 +11,7 @@ import ( "os" "sync" + "github.com/amnezia-vpn/amneziawg-windows-client/updater" "github.com/amnezia-vpn/amneziawg-windows/conf" ) @@ -34,6 +35,8 @@ const ( TunnelChangeNotificationType NotificationType = iota TunnelsChangeNotificationType ManagerStoppingNotificationType + UpdateFoundNotificationType + UpdateProgressNotificationType ) type MethodType int @@ -50,6 +53,8 @@ const ( CreateMethodType TunnelsMethodType QuitMethodType + UpdateStateMethodType + UpdateMethodType ) var ( @@ -76,6 +81,18 @@ type ManagerStoppingCallback struct { var managerStoppingCallbacks = make(map[*ManagerStoppingCallback]bool) +type UpdateFoundCallback struct { + cb func(updateState UpdateState) +} + +var updateFoundCallbacks = make(map[*UpdateFoundCallback]bool) + +type UpdateProgressCallback struct { + cb func(dp updater.DownloadProgress) +} + +var updateProgressCallbacks = make(map[*UpdateProgressCallback]bool) + func InitializeIPCClient(reader, writer, events *os.File) { rpcDecoder = gob.NewDecoder(reader) rpcEncoder = gob.NewEncoder(writer) @@ -128,6 +145,44 @@ func InitializeIPCClient(reader, writer, events *os.File) { for cb := range managerStoppingCallbacks { cb.cb() } + case UpdateFoundNotificationType: + var state UpdateState + err = decoder.Decode(&state) + if err != nil { + continue + } + for cb := range updateFoundCallbacks { + cb.cb(state) + } + case UpdateProgressNotificationType: + var dp updater.DownloadProgress + err = decoder.Decode(&dp.Activity) + if err != nil { + continue + } + err = decoder.Decode(&dp.BytesDownloaded) + if err != nil { + continue + } + err = decoder.Decode(&dp.BytesTotal) + if err != nil { + continue + } + var errStr string + err = decoder.Decode(&errStr) + if err != nil { + continue + } + if len(errStr) > 0 { + dp.Error = errors.New(errStr) + } + err = decoder.Decode(&dp.Complete) + if err != nil { + continue + } + for cb := range updateProgressCallbacks { + cb.cb(dp) + } } } }() @@ -354,6 +409,28 @@ func IPCClientQuit(stopTunnelsOnQuit bool) (alreadyQuit bool, err error) { return } +func IPCClientUpdateState() (updateState UpdateState, err error) { + rpcMutex.Lock() + defer rpcMutex.Unlock() + + err = rpcEncoder.Encode(UpdateStateMethodType) + if err != nil { + return + } + err = rpcDecoder.Decode(&updateState) + if err != nil { + return + } + return +} + +func IPCClientUpdate() error { + rpcMutex.Lock() + defer rpcMutex.Unlock() + + return rpcEncoder.Encode(UpdateMethodType) +} + func IPCClientRegisterTunnelChange(cb func(tunnel *Tunnel, state, globalState TunnelState, err error)) *TunnelChangeCallback { s := &TunnelChangeCallback{cb} tunnelChangeCallbacks[s] = true @@ -383,3 +460,23 @@ func IPCClientRegisterManagerStopping(cb func()) *ManagerStoppingCallback { func (cb *ManagerStoppingCallback) Unregister() { delete(managerStoppingCallbacks, cb) } + +func IPCClientRegisterUpdateFound(cb func(updateState UpdateState)) *UpdateFoundCallback { + s := &UpdateFoundCallback{cb} + updateFoundCallbacks[s] = true + return s +} + +func (cb *UpdateFoundCallback) Unregister() { + delete(updateFoundCallbacks, cb) +} + +func IPCClientRegisterUpdateProgress(cb func(dp updater.DownloadProgress)) *UpdateProgressCallback { + s := &UpdateProgressCallback{cb} + updateProgressCallbacks[s] = true + return s +} + +func (cb *UpdateProgressCallback) Unregister() { + delete(updateProgressCallbacks, cb) +} diff --git a/manager/ipc_server.go b/manager/ipc_server.go index b730b85..684e93c 100644 --- a/manager/ipc_server.go +++ b/manager/ipc_server.go @@ -19,6 +19,7 @@ import ( "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" + "github.com/amnezia-vpn/amneziawg-windows-client/updater" "github.com/amnezia-vpn/amneziawg-windows/conf" "github.com/amnezia-vpn/amneziawg-windows/services" ) @@ -268,6 +269,26 @@ func (s *ManagerService) Quit(stopTunnelsOnQuit bool) (alreadyQuit bool, err err return false, nil } +func (s *ManagerService) UpdateState() UpdateState { + return updateState +} + +func (s *ManagerService) Update() { + if s.elevatedToken == 0 { + return + } + progress := updater.DownloadVerifyAndExecute(uintptr(s.elevatedToken)) + go func() { + for { + dp := <-progress + IPCServerNotifyUpdateProgress(dp) + if dp.Complete || dp.Error != nil { + return + } + } + }() +} + func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) { decoder := gob.NewDecoder(reader) encoder := gob.NewEncoder(writer) @@ -422,6 +443,14 @@ func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) { if err != nil { return } + case UpdateStateMethodType: + updateState := s.UpdateState() + err = encoder.Encode(updateState) + if err != nil { + return + } + case UpdateMethodType: + s.Update() default: return } @@ -498,6 +527,14 @@ func IPCServerNotifyTunnelsChange() { notifyAll(TunnelsChangeNotificationType, false) } +func IPCServerNotifyUpdateFound(state UpdateState) { + notifyAll(UpdateFoundNotificationType, false, state) +} + +func IPCServerNotifyUpdateProgress(dp updater.DownloadProgress) { + notifyAll(UpdateProgressNotificationType, true, dp.Activity, dp.BytesDownloaded, dp.BytesTotal, errToString(dp.Error), dp.Complete) +} + func IPCServerNotifyManagerStopping() { notifyAll(ManagerStoppingNotificationType, false) time.Sleep(time.Millisecond * 200) diff --git a/manager/service.go b/manager/service.go index 3b48731..ca7bc4a 100644 --- a/manager/service.go +++ b/manager/service.go @@ -259,6 +259,8 @@ func (service *managerService) Execute(args []string, r <-chan svc.ChangeRequest }() } + go checkForUpdates() + var sessionsPointer *windows.WTS_SESSION_INFO var count uint32 err = windows.WTSEnumerateSessions(0, 0, 1, &sessionsPointer, &count) diff --git a/manager/updatestate.go b/manager/updatestate.go new file mode 100644 index 0000000..7a74fe4 --- /dev/null +++ b/manager/updatestate.go @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package manager + +import ( + "log" + "time" + _ "unsafe" + + "github.com/amnezia-vpn/amneziawg-windows-client/services" + "github.com/amnezia-vpn/amneziawg-windows-client/updater" + "github.com/amnezia-vpn/amneziawg-windows-client/version" +) + +//go:linkname fastrandn runtime.fastrandn +func fastrandn(n uint32) uint32 + +type UpdateState uint32 + +const ( + UpdateStateUnknown UpdateState = iota + UpdateStateFoundUpdate + UpdateStateUpdatesDisabledUnofficialBuild +) + +var updateState = UpdateStateUnknown + +func jitterSleep(min, max time.Duration) { + time.Sleep(min + time.Millisecond*time.Duration(fastrandn(uint32((max-min+1)/time.Millisecond)))) +} + +func checkForUpdates() { + if !version.IsRunningOfficialVersion() { + log.Println("Build is not official, so updates are disabled") + updateState = UpdateStateUpdatesDisabledUnofficialBuild + IPCServerNotifyUpdateFound(updateState) + return + } + if services.StartedAtBoot() { + jitterSleep(time.Minute*2, time.Minute*5) + } + noError, didNotify := true, false + for { + update, err := updater.CheckForUpdate() + if err == nil && update != nil && !didNotify { + log.Println("An update is available") + updateState = UpdateStateFoundUpdate + IPCServerNotifyUpdateFound(updateState) + didNotify = true + } else if err != nil && !didNotify { + log.Printf("Update checker: %v", err) + if noError { + jitterSleep(time.Minute*4, time.Minute*6) + noError = false + } else { + jitterSleep(time.Minute*25, time.Minute*30) + } + } else { + jitterSleep(time.Hour-time.Minute*3, time.Hour+time.Minute*3) + } + } +} diff --git a/ui/managewindow.go b/ui/managewindow.go index 6b6a976..0d6add0 100644 --- a/ui/managewindow.go +++ b/ui/managewindow.go @@ -23,6 +23,7 @@ type ManageTunnelsWindow struct { tabs *walk.TabWidget tunnelsPage *TunnelsPage logPage *LogPage + updatePage *UpdatePage tunnelChangedCB *manager.TunnelChangeCallback } @@ -174,6 +175,20 @@ func (mtw *ManageTunnelsWindow) onTunnelChange(tunnel *manager.Tunnel, state, gl }) } +func (mtw *ManageTunnelsWindow) UpdateFound() { + if mtw.updatePage != nil { + return + } + if IsAdmin { + mtw.SetTitle(l18n.Sprintf("%s (out of date)", mtw.Title())) + } + updatePage, err := NewUpdatePage() + if err == nil { + mtw.updatePage = updatePage + mtw.tabs.Pages().Add(updatePage.TabPage) + } +} + func (mtw *ManageTunnelsWindow) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case win.WM_QUERYENDSESSION: diff --git a/ui/tray.go b/ui/tray.go index f1d3ce9..7effd96 100644 --- a/ui/tray.go +++ b/ui/tray.go @@ -8,6 +8,7 @@ package ui import ( "sort" "strings" + "time" "github.com/amnezia-vpn/amneziawg-windows-client/l18n" "github.com/amnezia-vpn/amneziawg-windows-client/manager" @@ -339,6 +340,38 @@ func (tray *Tray) setTunnelState(tunnel *manager.Tunnel, state manager.TunnelSta } } +func (tray *Tray) UpdateFound() { + action := walk.NewAction() + action.SetText(l18n.Sprintf("An Update is Available!")) + menuIcon, _ := loadShieldIcon(16) + action.SetImage(menuIcon) + action.SetDefault(true) + showUpdateTab := func() { + if !tray.mtw.Visible() { + tray.mtw.tunnelsPage.listView.SelectFirstActiveTunnel() + } + tray.mtw.tabs.SetCurrentIndex(2) + raise(tray.mtw.Handle()) + } + action.Triggered().Attach(showUpdateTab) + tray.clicked = showUpdateTab + tray.ContextMenu().Actions().Insert(tray.ContextMenu().Actions().Len()-2, action) + + showUpdateBalloon := func() { + icon, _ := loadShieldIcon(128) + tray.ShowCustom(l18n.Sprintf("AmneziaWG Update Available"), l18n.Sprintf("An update to AmneziaWG is now available. You are advised to update as soon as possible."), icon) + } + + timeSinceStart := time.Now().Sub(startTime) + if timeSinceStart < time.Second*3 { + time.AfterFunc(time.Second*3-timeSinceStart, func() { + tray.mtw.Synchronize(showUpdateBalloon) + }) + } else { + showUpdateBalloon() + } +} + func (tray *Tray) onManageTunnels() { tray.mtw.tunnelsPage.listView.SelectFirstActiveTunnel() tray.mtw.tabs.SetCurrentIndex(0) diff --git a/ui/ui.go b/ui/ui.go index f4a6a03..2b74b07 100755 --- a/ui/ui.go +++ b/ui/ui.go @@ -67,6 +67,30 @@ func RunUI() { }) }) + onUpdateNotification := func(updateState manager.UpdateState) { + if updateState == manager.UpdateStateUnknown { + return + } + mtw.Synchronize(func() { + switch updateState { + case manager.UpdateStateFoundUpdate: + mtw.UpdateFound() + if tray != nil && IsAdmin { + tray.UpdateFound() + } + case manager.UpdateStateUpdatesDisabledUnofficialBuild: + mtw.SetTitle(l18n.Sprintf("%s (unsigned build, no updates)", mtw.Title())) + } + }) + } + manager.IPCClientRegisterUpdateFound(onUpdateNotification) + go func() { + updateState, err := manager.IPCClientUpdateState() + if err == nil { + onUpdateNotification(updateState) + } + }() + if tray == nil { win.ShowWindow(mtw.Handle(), win.SW_MINIMIZE) } diff --git a/ui/updatepage.go b/ui/updatepage.go new file mode 100644 index 0000000..78a41f9 --- /dev/null +++ b/ui/updatepage.go @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package ui + +import ( + "github.com/lxn/walk" + + "github.com/amnezia-vpn/amneziawg-windows-client/l18n" + "github.com/amnezia-vpn/amneziawg-windows-client/manager" + "github.com/amnezia-vpn/amneziawg-windows-client/updater" +) + +type UpdatePage struct { + *walk.TabPage +} + +func NewUpdatePage() (*UpdatePage, error) { + var err error + var disposables walk.Disposables + defer disposables.Treat() + + up := &UpdatePage{} + + if up.TabPage, err = walk.NewTabPage(); err != nil { + return nil, err + } + disposables.Add(up) + + up.SetTitle(l18n.Sprintf("An Update is Available!")) + + tabIcon, _ := loadShieldIcon(16) + up.SetImage(tabIcon) + + up.SetLayout(walk.NewVBoxLayout()) + + instructions, err := walk.NewTextLabel(up) + if err != nil { + return nil, err + } + instructions.SetText(l18n.Sprintf("An update to AmneziaWG is available. It is highly advisable to update without delay.")) + instructions.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0}) + + status, err := walk.NewTextLabel(up) + if err != nil { + return nil, err + } + status.SetText(l18n.Sprintf("Status: Waiting for user")) + status.SetMinMaxSize(walk.Size{1, 0}, walk.Size{0, 0}) + + bar, err := walk.NewProgressBar(up) + if err != nil { + return nil, err + } + bar.SetVisible(false) + + button, err := walk.NewPushButton(up) + if err != nil { + return nil, err + } + updateIcon, _ := loadSystemIcon("shell32", -47, 32) + button.SetImage(updateIcon) + button.SetText(l18n.Sprintf("Update Now")) + + if !IsAdmin { + button.SetText(l18n.Sprintf("Please ask the system administrator to update.")) + button.SetEnabled(false) + status.SetText(l18n.Sprintf("Status: Waiting for administrator")) + } + + walk.NewVSpacer(up) + + switchToUpdatingState := func() { + if !bar.Visible() { + up.SetSuspended(true) + button.SetEnabled(false) + button.SetVisible(false) + bar.SetVisible(true) + bar.SetMarqueeMode(true) + up.SetSuspended(false) + status.SetText(l18n.Sprintf("Status: Waiting for updater service")) + } + } + + switchToReadyState := func() { + if bar.Visible() { + up.SetSuspended(true) + bar.SetVisible(false) + bar.SetValue(0) + bar.SetRange(0, 1) + bar.SetMarqueeMode(false) + button.SetVisible(true) + button.SetEnabled(true) + up.SetSuspended(false) + } + } + + button.Clicked().Attach(func() { + switchToUpdatingState() + err := manager.IPCClientUpdate() + if err != nil { + switchToReadyState() + status.SetText(l18n.Sprintf("Error: %v. Please try again.", err)) + } + }) + + manager.IPCClientRegisterUpdateProgress(func(dp updater.DownloadProgress) { + up.Synchronize(func() { + switchToUpdatingState() + if dp.Error != nil { + switchToReadyState() + err := dp.Error + status.SetText(l18n.Sprintf("Error: %v. Please try again.", err)) + return + } + if len(dp.Activity) > 0 { + stateText := dp.Activity + status.SetText(l18n.Sprintf("Status: %s", stateText)) + } + if dp.BytesTotal > 0 { + bar.SetMarqueeMode(false) + bar.SetRange(0, int(dp.BytesTotal)) + bar.SetValue(int(dp.BytesDownloaded)) + } else { + bar.SetMarqueeMode(true) + bar.SetValue(0) + bar.SetRange(0, 1) + } + if dp.Complete { + switchToReadyState() + status.SetText(l18n.Sprintf("Status: Complete!")) + return + } + }) + }) + + disposables.Spare() + + return up, nil +} diff --git a/updater/authenticode.go b/updater/authenticode.go new file mode 100644 index 0000000..1e0a25c --- /dev/null +++ b/updater/authenticode.go @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +func verifyAuthenticode(path string) bool { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return false + } + data := &windows.WinTrustData{ + Size: uint32(unsafe.Sizeof(windows.WinTrustData{})), + UIChoice: windows.WTD_UI_NONE, + RevocationChecks: windows.WTD_REVOKE_WHOLECHAIN, // Full revocation checking, as this is called with network connectivity. + UnionChoice: windows.WTD_CHOICE_FILE, + StateAction: windows.WTD_STATEACTION_VERIFY, + FileOrCatalogOrBlobOrSgnrOrCert: unsafe.Pointer(&windows.WinTrustFileInfo{ + Size: uint32(unsafe.Sizeof(windows.WinTrustFileInfo{})), + FilePath: path16, + }), + } + verified := windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) == nil + data.StateAction = windows.WTD_STATEACTION_CLOSE + windows.WinVerifyTrustEx(windows.InvalidHWND, &windows.WINTRUST_ACTION_GENERIC_VERIFY_V2, data) + return verified +} diff --git a/updater/constants.go b/updater/constants.go new file mode 100644 index 0000000..cf0ced9 --- /dev/null +++ b/updater/constants.go @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +const ( + releasePublicKeyBase64 = "RWRNqGKtBXftKTKPpBPGDMe8jHLnFQ0EdRy8Wg0apV6vTDFLAODD83G4" + updateServerHost = "download.wireguard.com" + updateServerPort = 443 + updateServerUseHttps = true + latestVersionPath = "/windows-client/latest.sig" + msiPath = "/windows-client/%s" + msiArchPrefix = "wireguard-%s-" + msiSuffix = ".msi" +) diff --git a/updater/downloader.go b/updater/downloader.go new file mode 100644 index 0000000..9a1cf16 --- /dev/null +++ b/updater/downloader.go @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "crypto/hmac" + "errors" + "fmt" + "hash" + "io" + "sync/atomic" + + "golang.org/x/crypto/blake2b" + + "github.com/amnezia-vpn/amneziawg-windows-client/elevate" + "github.com/amnezia-vpn/amneziawg-windows-client/updater/winhttp" + "github.com/amnezia-vpn/amneziawg-windows-client/version" +) + +type DownloadProgress struct { + Activity string + BytesDownloaded uint64 + BytesTotal uint64 + Error error + Complete bool +} + +type progressHashWatcher struct { + dp *DownloadProgress + c chan DownloadProgress + hashState hash.Hash +} + +func (pm *progressHashWatcher) Write(p []byte) (int, error) { + bytes := len(p) + pm.dp.BytesDownloaded += uint64(bytes) + pm.c <- *pm.dp + pm.hashState.Write(p) + return bytes, nil +} + +type UpdateFound struct { + name string + hash [blake2b.Size256]byte +} + +func CheckForUpdate() (updateFound *UpdateFound, err error) { + updateFound, _, _, err = checkForUpdate(false) + return +} + +func checkForUpdate(keepSession bool) (*UpdateFound, *winhttp.Session, *winhttp.Connection, error) { + if !version.IsRunningOfficialVersion() { + return nil, nil, nil, errors.New("Build is not official, so updates are disabled") + } + session, err := winhttp.NewSession(version.UserAgent()) + if err != nil { + return nil, nil, nil, err + } + defer func() { + if err != nil || !keepSession { + session.Close() + } + }() + connection, err := session.Connect(updateServerHost, updateServerPort, updateServerUseHttps) + if err != nil { + return nil, nil, nil, err + } + defer func() { + if err != nil || !keepSession { + connection.Close() + } + }() + response, err := connection.Get(latestVersionPath, true) + if err != nil { + return nil, nil, nil, err + } + defer response.Close() + var fileList [1024 * 512] /* 512 KiB */ byte + bytesRead, err := response.Read(fileList[:]) + if err != nil && (err != io.EOF || bytesRead == 0) { + return nil, nil, nil, err + } + files, err := readFileList(fileList[:bytesRead]) + if err != nil { + return nil, nil, nil, err + } + updateFound, err := findCandidate(files) + if err != nil { + return nil, nil, nil, err + } + if keepSession { + return updateFound, session, connection, nil + } + return updateFound, nil, nil, nil +} + +var updateInProgress = uint32(0) + +func DownloadVerifyAndExecute(userToken uintptr) (progress chan DownloadProgress) { + progress = make(chan DownloadProgress, 128) + progress <- DownloadProgress{Activity: "Initializing"} + + if !atomic.CompareAndSwapUint32(&updateInProgress, 0, 1) { + progress <- DownloadProgress{Error: errors.New("An update is already in progress")} + return + } + + doIt := func() { + defer atomic.StoreUint32(&updateInProgress, 0) + + progress <- DownloadProgress{Activity: "Checking for update"} + update, session, connection, err := checkForUpdate(true) + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + defer connection.Close() + defer session.Close() + if update == nil { + progress <- DownloadProgress{Error: errors.New("No update was found")} + return + } + + progress <- DownloadProgress{Activity: "Creating temporary file"} + file, err := msiTempFile() + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + progress <- DownloadProgress{Activity: fmt.Sprintf("Msi destination is %#q", file.Name())} + defer func() { + if file != nil { + file.Delete() + } + }() + + dp := DownloadProgress{Activity: "Downloading update"} + progress <- dp + response, err := connection.Get(fmt.Sprintf(msiPath, update.name), false) + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + defer response.Close() + length, err := response.Length() + if err == nil && length >= 0 { + dp.BytesTotal = length + progress <- dp + } + hasher, err := blake2b.New256(nil) + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + pm := &progressHashWatcher{&dp, progress, hasher} + _, err = io.Copy(file, io.TeeReader(io.LimitReader(response, 1024*1024*100 /* 100 MiB */), pm)) + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + if !hmac.Equal(hasher.Sum(nil), update.hash[:]) { + progress <- DownloadProgress{Error: errors.New("The downloaded update has the wrong hash")} + return + } + + progress <- DownloadProgress{Activity: "Verifying authenticode signature"} + if !verifyAuthenticode(file.ExclusivePath()) { + progress <- DownloadProgress{Error: errors.New("The downloaded update does not have an authentic authenticode signature")} + return + } + + progress <- DownloadProgress{Activity: "Installing update"} + err = runMsi(file, userToken) + if err != nil { + progress <- DownloadProgress{Error: err} + return + } + + progress <- DownloadProgress{Complete: true} + } + if userToken == 0 { + go func() { + err := elevate.DoAsSystem(func() error { + doIt() + return nil + }) + if err != nil { + progress <- DownloadProgress{Error: err} + } + }() + } else { + go doIt() + } + + return progress +} diff --git a/updater/msirunner.go b/updater/msirunner.go new file mode 100644 index 0000000..ec6b1bd --- /dev/null +++ b/updater/msirunner.go @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type tempFile struct { + *os.File + originalHandle windows.Handle +} + +func (t *tempFile) ExclusivePath() string { + if t.originalHandle != 0 { + t.Close() // TODO: sort of a toctou, but msi requires unshared file + t.originalHandle = 0 + } + return t.Name() +} + +func (t *tempFile) Delete() error { + if t.originalHandle == 0 { + name16, err := windows.UTF16PtrFromString(t.Name()) + if err != nil { + return err + } + return windows.DeleteFile(name16) // TODO: how does this deal with reparse points? + } + disposition := byte(1) + err := windows.SetFileInformationByHandle(t.originalHandle, windows.FileDispositionInfo, &disposition, 1) + t.originalHandle = 0 + t.Close() + return err +} + +func runMsi(msi *tempFile, userToken uintptr) error { + system32, err := windows.GetSystemDirectory() + if err != nil { + return err + } + devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0) + if err != nil { + return err + } + defer devNull.Close() + msiPath := msi.ExclusivePath() + attr := &os.ProcAttr{ + Sys: &syscall.SysProcAttr{ + Token: syscall.Token(userToken), + }, + Files: []*os.File{devNull, devNull, devNull}, + Dir: filepath.Dir(msiPath), + } + msiexec := filepath.Join(system32, "msiexec.exe") + proc, err := os.StartProcess(msiexec, []string{msiexec, "/qb!-", "/i", filepath.Base(msiPath)}, attr) + if err != nil { + return err + } + state, err := proc.Wait() + if err != nil { + return err + } + if !state.Success() { + return &exec.ExitError{ProcessState: state} + } + return nil +} + +func msiTempFile() (*tempFile, error) { + var randBytes [32]byte + n, err := rand.Read(randBytes[:]) + if err != nil { + return nil, err + } + if n != int(len(randBytes)) { + return nil, errors.New("Unable to generate random bytes") + } + sd, err := windows.SecurityDescriptorFromString("O:SYD:PAI(A;;FA;;;SY)(A;;FR;;;BA)") + if err != nil { + return nil, err + } + sa := &windows.SecurityAttributes{ + Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), + SecurityDescriptor: sd, + } + windir, err := windows.GetWindowsDirectory() + if err != nil { + return nil, err + } + name := filepath.Join(windir, "Temp", hex.EncodeToString(randBytes[:])) + name16 := windows.StringToUTF16Ptr(name) + fileHandle, err := windows.CreateFile(name16, windows.GENERIC_WRITE|windows.DELETE, 0, sa, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_TEMPORARY, 0) + runtime.KeepAlive(sd) + if err != nil { + return nil, err + } + windows.MoveFileEx(name16, nil, windows.MOVEFILE_DELAY_UNTIL_REBOOT) + return &tempFile{ + File: os.NewFile(uintptr(fileHandle), name), + originalHandle: fileHandle, + }, nil +} diff --git a/updater/signify.go b/updater/signify.go new file mode 100644 index 0000000..de50fcb --- /dev/null +++ b/updater/signify.go @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "encoding/hex" + "errors" + "strings" + + "golang.org/x/crypto/blake2b" +) + +/* + * Generate with: + * $ b2sum -l 256 *.msi > list + * $ signify -S -e -s release.sec -m list + * $ upload ./list.sec + */ + +type fileList map[string][blake2b.Size256]byte + +func readFileList(input []byte) (fileList, error) { + publicKeyBytes, err := base64.StdEncoding.DecodeString(releasePublicKeyBase64) + if err != nil || len(publicKeyBytes) != ed25519.PublicKeySize+10 || publicKeyBytes[0] != 'E' || publicKeyBytes[1] != 'd' { + return nil, errors.New("Invalid public key") + } + lines := bytes.SplitN(input, []byte{'\n'}, 3) + if len(lines) != 3 { + return nil, errors.New("Signature input has too few lines") + } + if !bytes.HasPrefix(lines[0], []byte("untrusted comment: ")) { + return nil, errors.New("Signature input is missing untrusted comment") + } + signatureBytes, err := base64.StdEncoding.DecodeString(string(lines[1])) + if err != nil { + return nil, errors.New("Signature input is not valid base64") + } + if len(signatureBytes) != ed25519.SignatureSize+10 || !bytes.Equal(signatureBytes[:10], publicKeyBytes[:10]) { + return nil, errors.New("Signature input bytes are incorrect length, type, or keyid") + } + if !ed25519.Verify(publicKeyBytes[10:], lines[2], signatureBytes[10:]) { + return nil, errors.New("Signature is invalid") + } + fileLines := strings.Split(string(lines[2]), "\n") + fileHashes := make(map[string][blake2b.Size256]byte, len(fileLines)) + for index, line := range fileLines { + if len(line) == 0 && index == len(fileLines)-1 { + break + } + first, second, ok := strings.Cut(line, " ") + if !ok { + return nil, errors.New("File hash line has too few components") + } + maybeHash, err := hex.DecodeString(first) + if err != nil || len(maybeHash) != blake2b.Size256 { + return nil, errors.New("File hash is invalid base64 or incorrect number of bytes") + } + var hash [blake2b.Size256]byte + copy(hash[:], maybeHash) + fileHashes[second] = hash + } + if len(fileHashes) == 0 { + return nil, errors.New("No file hashes found in signed input") + } + return fileHashes, nil +} diff --git a/updater/updater_test.go b/updater/updater_test.go new file mode 100644 index 0000000..809262b --- /dev/null +++ b/updater/updater_test.go @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "testing" +) + +func TestUpdate(t *testing.T) { + update, err := CheckForUpdate() + if err != nil { + t.Error(err) + return + } + if update == nil { + t.Error("No update available") + return + } + t.Log("Found update") + progress := DownloadVerifyAndExecute(0) + for { + dp := <-progress + if dp.Error != nil { + t.Error(dp.Error) + return + } + if len(dp.Activity) > 0 { + t.Log(dp.Activity) + } + if dp.BytesTotal > 0 { + t.Logf("Downloaded %d of %d", dp.BytesDownloaded, dp.BytesTotal) + } + if dp.Complete { + t.Log("Complete!") + break + } + } +} diff --git a/updater/versions.go b/updater/versions.go new file mode 100644 index 0000000..f7608c7 --- /dev/null +++ b/updater/versions.go @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package updater + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/amnezia-vpn/amneziawg-windows-client/version" +) + +func versionNewerThanUs(candidate string) (bool, error) { + candidateParts := strings.Split(candidate, ".") + ourParts := strings.Split(version.Number, ".") + if len(candidateParts) == 0 || len(ourParts) == 0 { + return false, errors.New("Empty version") + } + l := len(candidateParts) + if len(ourParts) > l { + l = len(ourParts) + } + for i := 0; i < l; i++ { + var err error + cP, oP := uint64(0), uint64(0) + if i < len(candidateParts) { + if len(candidateParts[i]) == 0 { + return false, errors.New("Empty version part") + } + cP, err = strconv.ParseUint(candidateParts[i], 10, 16) + if err != nil { + return false, errors.New("Invalid version integer part") + } + } + if i < len(ourParts) { + if len(ourParts[i]) == 0 { + return false, errors.New("Empty version part") + } + oP, err = strconv.ParseUint(ourParts[i], 10, 16) + if err != nil { + return false, errors.New("Invalid version integer part") + } + } + if cP == oP { + continue + } + return cP > oP, nil + } + return false, nil +} + +func findCandidate(candidates fileList) (*UpdateFound, error) { + prefix := fmt.Sprintf(msiArchPrefix, version.Arch()) + suffix := msiSuffix + for name, hash := range candidates { + if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) { + version := strings.TrimSuffix(strings.TrimPrefix(name, prefix), suffix) + if len(version) > 128 { + return nil, errors.New("Version length is too long") + } + newer, err := versionNewerThanUs(version) + if err != nil { + return nil, err + } + if newer { + return &UpdateFound{name, hash}, nil + } + } + } + return nil, nil +} diff --git a/updater/winhttp/mksyscall.go b/updater/winhttp/mksyscall.go new file mode 100644 index 0000000..54c06e8 --- /dev/null +++ b/updater/winhttp/mksyscall.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package winhttp + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go diff --git a/updater/winhttp/syscall_windows.go b/updater/winhttp/syscall_windows.go new file mode 100644 index 0000000..4f967bf --- /dev/null +++ b/updater/winhttp/syscall_windows.go @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package winhttp + +import ( + "golang.org/x/sys/windows" +) + +type _HINTERNET windows.Handle + +type Error uint32 + +const ( + _WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0 + _WINHTTP_ACCESS_TYPE_NO_PROXY = 1 + _WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3 + _WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4 + + _WINHTTP_FLAG_ASYNC = 0x10000000 + + _WINHTTP_INVALID_STATUS_CALLBACK = ^uintptr(0) + + _WINHTTP_CALLBACK_STATUS_RESOLVING_NAME = 0x00000001 + _WINHTTP_CALLBACK_STATUS_NAME_RESOLVED = 0x00000002 + _WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER = 0x00000004 + _WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER = 0x00000008 + _WINHTTP_CALLBACK_STATUS_SENDING_REQUEST = 0x00000010 + _WINHTTP_CALLBACK_STATUS_REQUEST_SENT = 0x00000020 + _WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE = 0x00000040 + _WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED = 0x00000080 + _WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION = 0x00000100 + _WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED = 0x00000200 + _WINHTTP_CALLBACK_STATUS_HANDLE_CREATED = 0x00000400 + _WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING = 0x00000800 + _WINHTTP_CALLBACK_STATUS_DETECTING_PROXY = 0x00001000 + _WINHTTP_CALLBACK_STATUS_REDIRECT = 0x00004000 + _WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE = 0x00008000 + _WINHTTP_CALLBACK_STATUS_SECURE_FAILURE = 0x00010000 + _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE = 0x00020000 + _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE = 0x00040000 + _WINHTTP_CALLBACK_STATUS_READ_COMPLETE = 0x00080000 + _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE = 0x00100000 + _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR = 0x00200000 + _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE = 0x00400000 + _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE = 0x01000000 + _WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE = 0x02000000 + _WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE = 0x04000000 + _WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE = 0x10000000 + _WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE = 0x20000000 + + _WINHTTP_CALLBACK_FLAG_RESOLVE_NAME = _WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | _WINHTTP_CALLBACK_STATUS_NAME_RESOLVED + _WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER = _WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | _WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER + _WINHTTP_CALLBACK_FLAG_SEND_REQUEST = _WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | _WINHTTP_CALLBACK_STATUS_REQUEST_SENT + _WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE = _WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | _WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED + _WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION = _WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | _WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED + _WINHTTP_CALLBACK_FLAG_HANDLES = _WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | _WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + _WINHTTP_CALLBACK_FLAG_DETECTING_PROXY = _WINHTTP_CALLBACK_STATUS_DETECTING_PROXY + _WINHTTP_CALLBACK_FLAG_REDIRECT = _WINHTTP_CALLBACK_STATUS_REDIRECT + _WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE = _WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE + _WINHTTP_CALLBACK_FLAG_SECURE_FAILURE = _WINHTTP_CALLBACK_STATUS_SECURE_FAILURE + _WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE = _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE + _WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE = _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE + _WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE = _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE + _WINHTTP_CALLBACK_FLAG_READ_COMPLETE = _WINHTTP_CALLBACK_STATUS_READ_COMPLETE + _WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE = _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE + _WINHTTP_CALLBACK_FLAG_REQUEST_ERROR = _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR + _WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE = _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE + _WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS = _WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE | _WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | _WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE | _WINHTTP_CALLBACK_STATUS_READ_COMPLETE | _WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE | _WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | _WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE + _WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS = 0xffffffff + + _INTERNET_DEFAULT_PORT = 0 + _INTERNET_DEFAULT_HTTP_PORT = 80 + _INTERNET_DEFAULT_HTTPS_PORT = 443 + + _WINHTTP_FLAG_SECURE = 0x00800000 + _WINHTTP_FLAG_ESCAPE_PERCENT = 0x00000004 + _WINHTTP_FLAG_NULL_CODEPAGE = 0x00000008 + _WINHTTP_FLAG_BYPASS_PROXY_CACHE = 0x00000100 + _WINHTTP_FLAG_REFRESH = _WINHTTP_FLAG_BYPASS_PROXY_CACHE + _WINHTTP_FLAG_ESCAPE_DISABLE = 0x00000040 + _WINHTTP_FLAG_ESCAPE_DISABLE_QUERY = 0x00000080 + + _WINHTTP_QUERY_MIME_VERSION = 0 + _WINHTTP_QUERY_CONTENT_TYPE = 1 + _WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING = 2 + _WINHTTP_QUERY_CONTENT_ID = 3 + _WINHTTP_QUERY_CONTENT_DESCRIPTION = 4 + _WINHTTP_QUERY_CONTENT_LENGTH = 5 + _WINHTTP_QUERY_CONTENT_LANGUAGE = 6 + _WINHTTP_QUERY_ALLOW = 7 + _WINHTTP_QUERY_PUBLIC = 8 + _WINHTTP_QUERY_DATE = 9 + _WINHTTP_QUERY_EXPIRES = 10 + _WINHTTP_QUERY_LAST_MODIFIED = 11 + _WINHTTP_QUERY_MESSAGE_ID = 12 + _WINHTTP_QUERY_URI = 13 + _WINHTTP_QUERY_DERIVED_FROM = 14 + _WINHTTP_QUERY_COST = 15 + _WINHTTP_QUERY_LINK = 16 + _WINHTTP_QUERY_PRAGMA = 17 + _WINHTTP_QUERY_VERSION = 18 + _WINHTTP_QUERY_STATUS_CODE = 19 + _WINHTTP_QUERY_STATUS_TEXT = 20 + _WINHTTP_QUERY_RAW_HEADERS = 21 + _WINHTTP_QUERY_RAW_HEADERS_CRLF = 22 + _WINHTTP_QUERY_CONNECTION = 23 + _WINHTTP_QUERY_ACCEPT = 24 + _WINHTTP_QUERY_ACCEPT_CHARSET = 25 + _WINHTTP_QUERY_ACCEPT_ENCODING = 26 + _WINHTTP_QUERY_ACCEPT_LANGUAGE = 27 + _WINHTTP_QUERY_AUTHORIZATION = 28 + _WINHTTP_QUERY_CONTENT_ENCODING = 29 + _WINHTTP_QUERY_FORWARDED = 30 + _WINHTTP_QUERY_FROM = 31 + _WINHTTP_QUERY_IF_MODIFIED_SINCE = 32 + _WINHTTP_QUERY_LOCATION = 33 + _WINHTTP_QUERY_ORIG_URI = 34 + _WINHTTP_QUERY_REFERER = 35 + _WINHTTP_QUERY_RETRY_AFTER = 36 + _WINHTTP_QUERY_SERVER = 37 + _WINHTTP_QUERY_TITLE = 38 + _WINHTTP_QUERY_USER_AGENT = 39 + _WINHTTP_QUERY_WWW_AUTHENTICATE = 40 + _WINHTTP_QUERY_PROXY_AUTHENTICATE = 41 + _WINHTTP_QUERY_ACCEPT_RANGES = 42 + _WINHTTP_QUERY_SET_COOKIE = 43 + _WINHTTP_QUERY_COOKIE = 44 + _WINHTTP_QUERY_REQUEST_METHOD = 45 + _WINHTTP_QUERY_REFRESH = 46 + _WINHTTP_QUERY_CONTENT_DISPOSITION = 47 + _WINHTTP_QUERY_AGE = 48 + _WINHTTP_QUERY_CACHE_CONTROL = 49 + _WINHTTP_QUERY_CONTENT_BASE = 50 + _WINHTTP_QUERY_CONTENT_LOCATION = 51 + _WINHTTP_QUERY_CONTENT_MD5 = 52 + _WINHTTP_QUERY_CONTENT_RANGE = 53 + _WINHTTP_QUERY_ETAG = 54 + _WINHTTP_QUERY_HOST = 55 + _WINHTTP_QUERY_IF_MATCH = 56 + _WINHTTP_QUERY_IF_NONE_MATCH = 57 + _WINHTTP_QUERY_IF_RANGE = 58 + _WINHTTP_QUERY_IF_UNMODIFIED_SINCE = 59 + _WINHTTP_QUERY_MAX_FORWARDS = 60 + _WINHTTP_QUERY_PROXY_AUTHORIZATION = 61 + _WINHTTP_QUERY_RANGE = 62 + _WINHTTP_QUERY_TRANSFER_ENCODING = 63 + _WINHTTP_QUERY_UPGRADE = 64 + _WINHTTP_QUERY_VARY = 65 + _WINHTTP_QUERY_VIA = 66 + _WINHTTP_QUERY_WARNING = 67 + _WINHTTP_QUERY_EXPECT = 68 + _WINHTTP_QUERY_PROXY_CONNECTION = 69 + _WINHTTP_QUERY_UNLESS_MODIFIED_SINCE = 70 + _WINHTTP_QUERY_PROXY_SUPPORT = 75 + _WINHTTP_QUERY_AUTHENTICATION_INFO = 76 + _WINHTTP_QUERY_PASSPORT_URLS = 77 + _WINHTTP_QUERY_PASSPORT_CONFIG = 78 + _WINHTTP_QUERY_MAX = 78 + _WINHTTP_QUERY_CUSTOM = 65535 + _WINHTTP_QUERY_FLAG_REQUEST_HEADERS = 0x80000000 + _WINHTTP_QUERY_FLAG_SYSTEMTIME = 0x40000000 + _WINHTTP_QUERY_FLAG_NUMBER = 0x20000000 + _WINHTTP_QUERY_FLAG_NUMBER64 = 0x08000000 + + _WINHTTP_FIRST_OPTION = _WINHTTP_OPTION_CALLBACK + _WINHTTP_OPTION_CALLBACK = 1 + _WINHTTP_OPTION_RESOLVE_TIMEOUT = 2 + _WINHTTP_OPTION_CONNECT_TIMEOUT = 3 + _WINHTTP_OPTION_CONNECT_RETRIES = 4 + _WINHTTP_OPTION_SEND_TIMEOUT = 5 + _WINHTTP_OPTION_RECEIVE_TIMEOUT = 6 + _WINHTTP_OPTION_RECEIVE_RESPONSE_TIMEOUT = 7 + _WINHTTP_OPTION_HANDLE_TYPE = 9 + _WINHTTP_OPTION_READ_BUFFER_SIZE = 12 + _WINHTTP_OPTION_WRITE_BUFFER_SIZE = 13 + _WINHTTP_OPTION_PARENT_HANDLE = 21 + _WINHTTP_OPTION_EXTENDED_ERROR = 24 + _WINHTTP_OPTION_SECURITY_FLAGS = 31 + _WINHTTP_OPTION_SECURITY_CERTIFICATE_STRUCT = 32 + _WINHTTP_OPTION_URL = 34 + _WINHTTP_OPTION_SECURITY_KEY_BITNESS = 36 + _WINHTTP_OPTION_PROXY = 38 + _WINHTTP_OPTION_PROXY_RESULT_ENTRY = 39 + _WINHTTP_OPTION_USER_AGENT = 41 + _WINHTTP_OPTION_CONTEXT_VALUE = 45 + _WINHTTP_OPTION_CLIENT_CERT_CONTEXT = 47 + _WINHTTP_OPTION_REQUEST_PRIORITY = 58 + _WINHTTP_OPTION_HTTP_VERSION = 59 + _WINHTTP_OPTION_DISABLE_FEATURE = 63 + _WINHTTP_OPTION_CODEPAGE = 68 + _WINHTTP_OPTION_MAX_CONNS_PER_SERVER = 73 + _WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER = 74 + _WINHTTP_OPTION_AUTOLOGON_POLICY = 77 + _WINHTTP_OPTION_SERVER_CERT_CONTEXT = 78 + _WINHTTP_OPTION_ENABLE_FEATURE = 79 + _WINHTTP_OPTION_WORKER_THREAD_COUNT = 80 + _WINHTTP_OPTION_PASSPORT_COBRANDING_TEXT = 81 + _WINHTTP_OPTION_PASSPORT_COBRANDING_URL = 82 + _WINHTTP_OPTION_CONFIGURE_PASSPORT_AUTH = 83 + _WINHTTP_OPTION_SECURE_PROTOCOLS = 84 + _WINHTTP_OPTION_ENABLETRACING = 85 + _WINHTTP_OPTION_PASSPORT_SIGN_OUT = 86 + _WINHTTP_OPTION_PASSPORT_RETURN_URL = 87 + _WINHTTP_OPTION_REDIRECT_POLICY = 88 + _WINHTTP_OPTION_MAX_HTTP_AUTOMATIC_REDIRECTS = 89 + _WINHTTP_OPTION_MAX_HTTP_STATUS_CONTINUE = 90 + _WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE = 91 + _WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE = 92 + _WINHTTP_OPTION_CONNECTION_INFO = 93 + _WINHTTP_OPTION_CLIENT_CERT_ISSUER_LIST = 94 + _WINHTTP_OPTION_SPN = 96 + _WINHTTP_OPTION_GLOBAL_PROXY_CREDS = 97 + _WINHTTP_OPTION_GLOBAL_SERVER_CREDS = 98 + _WINHTTP_OPTION_UNLOAD_NOTIFY_EVENT = 99 + _WINHTTP_OPTION_REJECT_USERPWD_IN_URL = 100 + _WINHTTP_OPTION_USE_GLOBAL_SERVER_CREDENTIALS = 101 + _WINHTTP_OPTION_RECEIVE_PROXY_CONNECT_RESPONSE = 103 + _WINHTTP_OPTION_IS_PROXY_CONNECT_RESPONSE = 104 + _WINHTTP_OPTION_SERVER_SPN_USED = 106 + _WINHTTP_OPTION_PROXY_SPN_USED = 107 + _WINHTTP_OPTION_SERVER_CBT = 108 + _WINHTTP_OPTION_UNSAFE_HEADER_PARSING = 110 + _WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS = 111 + _WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114 + _WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115 + _WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL = 116 + _WINHTTP_OPTION_DECOMPRESSION = 118 + _WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE = 122 + _WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE = 123 + _WINHTTP_OPTION_TCP_PRIORITY_HINT = 128 + _WINHTTP_OPTION_CONNECTION_FILTER = 131 + _WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133 + _WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134 + _WINHTTP_OPTION_KDC_PROXY_SETTINGS = 136 + _WINHTTP_OPTION_ENCODE_EXTRA = 138 + _WINHTTP_OPTION_DISABLE_STREAM_QUEUE = 139 + _WINHTTP_OPTION_IPV6_FAST_FALLBACK = 140 + _WINHTTP_OPTION_CONNECTION_STATS_V0 = 141 + _WINHTTP_OPTION_REQUEST_TIMES = 142 + _WINHTTP_OPTION_EXPIRE_CONNECTION = 143 + _WINHTTP_OPTION_DISABLE_SECURE_PROTOCOL_FALLBACK = 144 + _WINHTTP_OPTION_HTTP_PROTOCOL_REQUIRED = 145 + _WINHTTP_OPTION_REQUEST_STATS = 146 + _WINHTTP_OPTION_SERVER_CERT_CHAIN_CONTEXT = 147 + _WINHTTP_LAST_OPTION = _WINHTTP_OPTION_SERVER_CERT_CHAIN_CONTEXT + + _ICU_ESCAPE = 0x80000000 + _ICU_ESCAPE_AUTHORITY = 0x00002000 + _ICU_REJECT_USERPWD = 0x00004000 + + _INTERNET_SCHEME_HTTP = 1 + _INTERNET_SCHEME_HTTPS = 2 + _INTERNET_SCHEME_FTP = 3 + _INTERNET_SCHEME_SOCKS = 4 + + _WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 = 0x00000008 + _WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 = 0x00000020 + _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 = 0x00000080 + _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 = 0x00000200 + _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 = 0x00000800 + _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 = 0x00002000 + _WINHTTP_FLAG_SECURE_PROTOCOL_ALL = _WINHTTP_FLAG_SECURE_PROTOCOL_SSL2 | _WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 + + _WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1 + + _WINHTTP_ERROR_BASE = 12000 + _ERROR_WINHTTP_OUT_OF_HANDLES = Error(12000 + 1) + _ERROR_WINHTTP_TIMEOUT = Error(12000 + 2) + _ERROR_WINHTTP_INTERNAL_ERROR = Error(12000 + 4) + _ERROR_WINHTTP_INVALID_URL = Error(12000 + 5) + _ERROR_WINHTTP_UNRECOGNIZED_SCHEME = Error(12000 + 6) + _ERROR_WINHTTP_NAME_NOT_RESOLVED = Error(12000 + 7) + _ERROR_WINHTTP_INVALID_OPTION = Error(12000 + 9) + _ERROR_WINHTTP_OPTION_NOT_SETTABLE = Error(12000 + 11) + _ERROR_WINHTTP_SHUTDOWN = Error(12000 + 12) + _ERROR_WINHTTP_LOGIN_FAILURE = Error(12000 + 15) + _ERROR_WINHTTP_OPERATION_CANCELLED = Error(12000 + 17) + _ERROR_WINHTTP_INCORRECT_HANDLE_TYPE = Error(12000 + 18) + _ERROR_WINHTTP_INCORRECT_HANDLE_STATE = Error(12000 + 19) + _ERROR_WINHTTP_CANNOT_CONNECT = Error(12000 + 29) + _ERROR_WINHTTP_CONNECTION_ERROR = Error(12000 + 30) + _ERROR_WINHTTP_RESEND_REQUEST = Error(12000 + 32) + _ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED = Error(12000 + 44) + _ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN = Error(12000 + 100) + _ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND = Error(12000 + 101) + _ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND = Error(12000 + 102) + _ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN = Error(12000 + 103) + _ERROR_WINHTTP_HEADER_NOT_FOUND = Error(12000 + 150) + _ERROR_WINHTTP_INVALID_SERVER_RESPONSE = Error(12000 + 152) + _ERROR_WINHTTP_INVALID_HEADER = Error(12000 + 153) + _ERROR_WINHTTP_INVALID_QUERY_REQUEST = Error(12000 + 154) + _ERROR_WINHTTP_HEADER_ALREADY_EXISTS = Error(12000 + 155) + _ERROR_WINHTTP_REDIRECT_FAILED = Error(12000 + 156) + _ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR = Error(12000 + 178) + _ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT = Error(12000 + 166) + _ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT = Error(12000 + 167) + _ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE = Error(12000 + 176) + _ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR = Error(12000 + 177) + _ERROR_WINHTTP_NOT_INITIALIZED = Error(12000 + 172) + _ERROR_WINHTTP_SECURE_FAILURE = Error(12000 + 175) + _ERROR_WINHTTP_SECURE_CERT_DATE_INVALID = Error(12000 + 37) + _ERROR_WINHTTP_SECURE_CERT_CN_INVALID = Error(12000 + 38) + _ERROR_WINHTTP_SECURE_INVALID_CA = Error(12000 + 45) + _ERROR_WINHTTP_SECURE_CERT_REV_FAILED = Error(12000 + 57) + _ERROR_WINHTTP_SECURE_CHANNEL_ERROR = Error(12000 + 157) + _ERROR_WINHTTP_SECURE_INVALID_CERT = Error(12000 + 169) + _ERROR_WINHTTP_SECURE_CERT_REVOKED = Error(12000 + 170) + _ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE = Error(12000 + 179) + _ERROR_WINHTTP_AUTODETECTION_FAILED = Error(12000 + 180) + _ERROR_WINHTTP_HEADER_COUNT_EXCEEDED = Error(12000 + 181) + _ERROR_WINHTTP_HEADER_SIZE_OVERFLOW = Error(12000 + 182) + _ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW = Error(12000 + 183) + _ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW = Error(12000 + 184) + _ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY = Error(12000 + 185) + _ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY = Error(12000 + 186) + _ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY = Error(12000 + 187) + _ERROR_WINHTTP_SECURE_FAILURE_PROXY = Error(12000 + 188) + _ERROR_WINHTTP_RESERVED_189 = Error(12000 + 189) + _ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH = Error(12000 + 190) + _WINHTTP_ERROR_LAST = _WINHTTP_ERROR_BASE + 190 +) + +type _URL_COMPONENTS struct { + structSize uint32 + scheme *uint16 + schemeLength uint32 + schemeType uint32 + hostName *uint16 + hostNameLength uint32 + port uint16 + username *uint16 + usernameLength uint32 + password *uint16 + passwordLength uint32 + urlPath *uint16 + urlPathLength uint32 + extraInfo *uint16 + extraInfoLength uint32 +} + +//sys winHttpOpen(userAgent *uint16, accessType uint32, proxy *uint16, proxyBypass *uint16, flags uint32) (sessionHandle _HINTERNET, err error) = winhttp.WinHttpOpen +//sys winHttpSetStatusCallback(handle _HINTERNET, callback uintptr, notificationFlags uint32, reserved uintptr) (previousCallback uintptr, err error) [failretval==_WINHTTP_INVALID_STATUS_CALLBACK] = winhttp.WinHttpSetStatusCallback +//sys winHttpCloseHandle(handle _HINTERNET) (err error) = winhttp.WinHttpCloseHandle +//sys winHttpConnect(sessionHandle _HINTERNET, serverName *uint16, serverPort uint16, reserved uint32) (handle _HINTERNET, err error) = winhttp.WinHttpConnect +//sys winHttpOpenRequest(connectHandle _HINTERNET, verb *uint16, objectName *uint16, version *uint16, referrer *uint16, acceptTypes **uint16, flags uint32) (requestHandle _HINTERNET, err error) = winhttp.WinHttpOpenRequest +//sys winHttpSendRequest(requestHandle _HINTERNET, headers *uint16, headersLength uint32, optional *byte, optionalLength uint32, totalLength uint32, context uintptr) (err error) = winhttp.WinHttpSendRequest +//sys winHttpReceiveResponse(requestHandle _HINTERNET, reserved uintptr) (err error) = winhttp.WinHttpReceiveResponse +//sys winHttpQueryHeaders(requestHandle _HINTERNET, infoLevel uint32, name *uint16, buffer unsafe.Pointer, bufferLen *uint32, index *uint32) (err error) = winhttp.WinHttpQueryHeaders +//sys winHttpQueryDataAvailable(requestHandle _HINTERNET, bytesAvailable *uint32) (err error) = winhttp.WinHttpQueryDataAvailable +//sys winHttpReadData(requestHandle _HINTERNET, buffer *byte, bufferSize uint32, bytesRead *uint32) (err error) = winhttp.WinHttpReadData +//sys winHttpCrackUrl(url *uint16, urlSize uint32, flags uint32, components *_URL_COMPONENTS) (err error) = winhttp.WinHttpCrackUrl +//sys winHttpSetOption(sessionOrRequestHandle _HINTERNET, option uint32, buffer unsafe.Pointer, bufferLen uint32) (err error) = winhttp.WinHttpSetOption diff --git a/updater/winhttp/winhttp.go b/updater/winhttp/winhttp.go new file mode 100644 index 0000000..cb19f19 --- /dev/null +++ b/updater/winhttp/winhttp.go @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package winhttp + +import ( + "errors" + "fmt" + "io" + "runtime" + "strconv" + "strings" + "sync/atomic" + "unsafe" + + "golang.org/x/sys/windows" +) + +type Session struct { + handle _HINTERNET +} + +type Connection struct { + handle _HINTERNET + session *Session + https bool +} + +type Response struct { + handle _HINTERNET + connection *Connection +} + +func convertError(err *error) { + if *err == nil { + return + } + var errno windows.Errno + if errors.As(*err, &errno) { + if errno > _WINHTTP_ERROR_BASE && errno <= _WINHTTP_ERROR_LAST { + *err = Error(errno) + } + } +} + +func isWin7() bool { + maj, min, _ := windows.RtlGetNtVersionNumbers() + return maj < 6 || (maj == 6 && min <= 1) +} + +func isWin8DotZeroOrBelow() bool { + maj, min, _ := windows.RtlGetNtVersionNumbers() + return maj < 6 || (maj == 6 && min <= 2) +} + +func NewSession(userAgent string) (session *Session, err error) { + session = new(Session) + defer convertError(&err) + defer func() { + if err != nil { + session.Close() + session = nil + } + }() + userAgent16, err := windows.UTF16PtrFromString(userAgent) + if err != nil { + return + } + var proxyFlag uint32 = _WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY + if isWin7() { + proxyFlag = _WINHTTP_ACCESS_TYPE_DEFAULT_PROXY + } + session.handle, err = winHttpOpen(userAgent16, proxyFlag, nil, nil, 0) + if err != nil { + return + } + var enableHttp2 uint32 = _WINHTTP_PROTOCOL_FLAG_HTTP2 + _ = winHttpSetOption(session.handle, _WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, unsafe.Pointer(&enableHttp2), uint32(unsafe.Sizeof(enableHttp2))) // Don't check return value, in case of old Windows + + if isWin8DotZeroOrBelow() { + var enableTLS12 uint32 = _WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 + err = winHttpSetOption(session.handle, _WINHTTP_OPTION_SECURE_PROTOCOLS, unsafe.Pointer(&enableTLS12), uint32(unsafe.Sizeof(enableTLS12))) + if err != nil { + return + } + } + + runtime.SetFinalizer(session, func(session *Session) { + session.Close() + }) + return +} + +func (session *Session) Close() (err error) { + defer convertError(&err) + handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&session.handle), 0)) + if handle == 0 { + return + } + return winHttpCloseHandle(handle) +} + +func (session *Session) Connect(server string, port uint16, https bool) (connection *Connection, err error) { + connection = &Connection{session: session} + defer convertError(&err) + defer func() { + if err != nil { + connection.Close() + connection = nil + } + }() + server16, err := windows.UTF16PtrFromString(server) + if err != nil { + return + } + connection.handle, err = winHttpConnect(session.handle, server16, port, 0) + if err != nil { + return + } + connection.https = https + + runtime.SetFinalizer(connection, func(connection *Connection) { + connection.Close() + }) + return +} + +func (connection *Connection) Close() (err error) { + defer convertError(&err) + handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&connection.handle), 0)) + if handle == 0 { + return + } + return winHttpCloseHandle(handle) +} + +func (connection *Connection) Get(path string, refresh bool) (response *Response, err error) { + response = &Response{connection: connection} + defer convertError(&err) + defer func() { + if err != nil { + response.Close() + response = nil + } + }() + var flags uint32 + if refresh { + flags |= _WINHTTP_FLAG_REFRESH + } + if connection.https { + flags |= _WINHTTP_FLAG_SECURE + } + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return + } + get16, err := windows.UTF16PtrFromString("GET") + if err != nil { + return + } + response.handle, err = winHttpOpenRequest(connection.handle, get16, path16, nil, nil, nil, flags) + if err != nil { + return + } + err = winHttpSendRequest(response.handle, nil, 0, nil, 0, 0, 0) + if err != nil { + return + } + err = winHttpReceiveResponse(response.handle, 0) + if err != nil { + return + } + + runtime.SetFinalizer(response, func(response *Response) { + response.Close() + }) + return +} + +func (response *Response) Length() (length uint64, err error) { + defer convertError(&err) + numBuf := make([]uint16, 22) + numLen := uint32(len(numBuf) * 2) + err = winHttpQueryHeaders(response.handle, _WINHTTP_QUERY_CONTENT_LENGTH, nil, unsafe.Pointer(&numBuf[0]), &numLen, nil) + if err != nil { + return + } + length, err = strconv.ParseUint(windows.UTF16ToString(numBuf[:numLen]), 10, 64) + if err != nil { + return + } + return +} + +func (response *Response) Read(p []byte) (n int, err error) { + defer convertError(&err) + if len(p) == 0 { + return 0, nil + } + var bytesRead uint32 + err = winHttpReadData(response.handle, &p[0], uint32(len(p)), &bytesRead) + if err != nil { + return 0, nil + } + if bytesRead == 0 || int(bytesRead) < 0 { + return 0, io.EOF + } + return int(bytesRead), nil +} + +func (response *Response) Close() (err error) { + defer convertError(&err) + handle := (_HINTERNET)(atomic.SwapUintptr((*uintptr)(&response.handle), 0)) + if handle == 0 { + return + } + return winHttpCloseHandle(handle) +} + +func (error Error) Error() string { + var message [2048]uint16 + n, err := windows.FormatMessage(windows.FORMAT_MESSAGE_FROM_HMODULE|windows.FORMAT_MESSAGE_IGNORE_INSERTS|windows.FORMAT_MESSAGE_MAX_WIDTH_MASK, + modwinhttp.Handle(), uint32(error), 0, message[:], nil) + if err != nil { + return fmt.Sprintf("WinHTTP error #%d", error) + } + return strings.TrimSpace(windows.UTF16ToString(message[:n])) +} diff --git a/updater/winhttp/winhttp_test.go b/updater/winhttp/winhttp_test.go new file mode 100644 index 0000000..36017a4 --- /dev/null +++ b/updater/winhttp/winhttp_test.go @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +package winhttp + +import ( + "fmt" + "io" + "runtime" + "testing" +) + +type progressPrinter struct { + downloaded uint64 + total uint64 +} + +func (pp *progressPrinter) Write(p []byte) (int, error) { + bytes := len(p) + pp.downloaded += uint64(bytes) + fmt.Printf("%d/%d bytes, %f%%\n", pp.downloaded, pp.total, float64(pp.downloaded)/float64(pp.total)*100.0) + return bytes, nil +} + +func TestResponse(t *testing.T) { + session, err := NewSession("WinHTTP Test Suite/1.0") + if err != nil { + t.Fatal(err) + } + connection, err := session.Connect("zx2c4.com", 443, true) + if err != nil { + t.Fatal(err) + } + r, err := connection.Get("/ip", true) + length, err := r.Length() + if err != nil { + t.Fatal(err) + } + fmt.Printf("The length is %d\n", length) + bytes, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + fmt.Println(string(bytes)) + r.Close() + + connection, err = session.Connect("speed.hetzner.de", 443, true) + if err != nil { + t.Fatal(err) + } + r, err = connection.Get("/10GB.bin", false) + if err != nil { + t.Fatal(err) + } + length, err = r.Length() + if err != nil { + t.Fatal(err) + } + amountRead, err := io.Copy(&progressPrinter{total: length}, r) + if err != nil { + t.Fatal(err) + } + r.Close() + if length != uint64(amountRead) { + t.Fatalf("Expected to read %d, but only read %d", length, amountRead) + } + + runtime.GC() // Try to force the finalizers to be called +} diff --git a/updater/winhttp/zsyscall_windows.go b/updater/winhttp/zsyscall_windows.go new file mode 100644 index 0000000..1c45994 --- /dev/null +++ b/updater/winhttp/zsyscall_windows.go @@ -0,0 +1,155 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package winhttp + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modwinhttp = windows.NewLazySystemDLL("winhttp.dll") + + procWinHttpCloseHandle = modwinhttp.NewProc("WinHttpCloseHandle") + procWinHttpConnect = modwinhttp.NewProc("WinHttpConnect") + procWinHttpCrackUrl = modwinhttp.NewProc("WinHttpCrackUrl") + procWinHttpOpen = modwinhttp.NewProc("WinHttpOpen") + procWinHttpOpenRequest = modwinhttp.NewProc("WinHttpOpenRequest") + procWinHttpQueryDataAvailable = modwinhttp.NewProc("WinHttpQueryDataAvailable") + procWinHttpQueryHeaders = modwinhttp.NewProc("WinHttpQueryHeaders") + procWinHttpReadData = modwinhttp.NewProc("WinHttpReadData") + procWinHttpReceiveResponse = modwinhttp.NewProc("WinHttpReceiveResponse") + procWinHttpSendRequest = modwinhttp.NewProc("WinHttpSendRequest") + procWinHttpSetOption = modwinhttp.NewProc("WinHttpSetOption") + procWinHttpSetStatusCallback = modwinhttp.NewProc("WinHttpSetStatusCallback") +) + +func winHttpCloseHandle(handle _HINTERNET) (err error) { + r1, _, e1 := syscall.Syscall(procWinHttpCloseHandle.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpConnect(sessionHandle _HINTERNET, serverName *uint16, serverPort uint16, reserved uint32) (handle _HINTERNET, err error) { + r0, _, e1 := syscall.Syscall6(procWinHttpConnect.Addr(), 4, uintptr(sessionHandle), uintptr(unsafe.Pointer(serverName)), uintptr(serverPort), uintptr(reserved), 0, 0) + handle = _HINTERNET(r0) + if handle == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpCrackUrl(url *uint16, urlSize uint32, flags uint32, components *_URL_COMPONENTS) (err error) { + r1, _, e1 := syscall.Syscall6(procWinHttpCrackUrl.Addr(), 4, uintptr(unsafe.Pointer(url)), uintptr(urlSize), uintptr(flags), uintptr(unsafe.Pointer(components)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpOpen(userAgent *uint16, accessType uint32, proxy *uint16, proxyBypass *uint16, flags uint32) (sessionHandle _HINTERNET, err error) { + r0, _, e1 := syscall.Syscall6(procWinHttpOpen.Addr(), 5, uintptr(unsafe.Pointer(userAgent)), uintptr(accessType), uintptr(unsafe.Pointer(proxy)), uintptr(unsafe.Pointer(proxyBypass)), uintptr(flags), 0) + sessionHandle = _HINTERNET(r0) + if sessionHandle == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpOpenRequest(connectHandle _HINTERNET, verb *uint16, objectName *uint16, version *uint16, referrer *uint16, acceptTypes **uint16, flags uint32) (requestHandle _HINTERNET, err error) { + r0, _, e1 := syscall.Syscall9(procWinHttpOpenRequest.Addr(), 7, uintptr(connectHandle), uintptr(unsafe.Pointer(verb)), uintptr(unsafe.Pointer(objectName)), uintptr(unsafe.Pointer(version)), uintptr(unsafe.Pointer(referrer)), uintptr(unsafe.Pointer(acceptTypes)), uintptr(flags), 0, 0) + requestHandle = _HINTERNET(r0) + if requestHandle == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpQueryDataAvailable(requestHandle _HINTERNET, bytesAvailable *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procWinHttpQueryDataAvailable.Addr(), 2, uintptr(requestHandle), uintptr(unsafe.Pointer(bytesAvailable)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpQueryHeaders(requestHandle _HINTERNET, infoLevel uint32, name *uint16, buffer unsafe.Pointer, bufferLen *uint32, index *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procWinHttpQueryHeaders.Addr(), 6, uintptr(requestHandle), uintptr(infoLevel), uintptr(unsafe.Pointer(name)), uintptr(buffer), uintptr(unsafe.Pointer(bufferLen)), uintptr(unsafe.Pointer(index))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpReadData(requestHandle _HINTERNET, buffer *byte, bufferSize uint32, bytesRead *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procWinHttpReadData.Addr(), 4, uintptr(requestHandle), uintptr(unsafe.Pointer(buffer)), uintptr(bufferSize), uintptr(unsafe.Pointer(bytesRead)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpReceiveResponse(requestHandle _HINTERNET, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procWinHttpReceiveResponse.Addr(), 2, uintptr(requestHandle), uintptr(reserved), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpSendRequest(requestHandle _HINTERNET, headers *uint16, headersLength uint32, optional *byte, optionalLength uint32, totalLength uint32, context uintptr) (err error) { + r1, _, e1 := syscall.Syscall9(procWinHttpSendRequest.Addr(), 7, uintptr(requestHandle), uintptr(unsafe.Pointer(headers)), uintptr(headersLength), uintptr(unsafe.Pointer(optional)), uintptr(optionalLength), uintptr(totalLength), uintptr(context), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpSetOption(sessionOrRequestHandle _HINTERNET, option uint32, buffer unsafe.Pointer, bufferLen uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procWinHttpSetOption.Addr(), 4, uintptr(sessionOrRequestHandle), uintptr(option), uintptr(buffer), uintptr(bufferLen), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func winHttpSetStatusCallback(handle _HINTERNET, callback uintptr, notificationFlags uint32, reserved uintptr) (previousCallback uintptr, err error) { + r0, _, e1 := syscall.Syscall6(procWinHttpSetStatusCallback.Addr(), 4, uintptr(handle), uintptr(callback), uintptr(notificationFlags), uintptr(reserved), 0, 0) + previousCallback = uintptr(r0) + if previousCallback == _WINHTTP_INVALID_STATUS_CALLBACK { + err = errnoErr(e1) + } + return +}