Shift+Ctrl+X for advanced users that likes to customize the image

- Adds "hidden" shift+ctrl+X shortcut for eXpert image customization
  options.

Allows one to set certain options on RPI OS images, namely:

* disable overscan
* set hostname
* enable ssh and
  - set Pi user password if using password authentication
  OR
  - set authorized_keys
    (if running Imager on Linux/Mac this will
     have contents of ~/.ssh/id_rsa.pub prefilled)
* configure wifi settings
  (if computer running Imager is connected by wifi it will
  prefill wifi SSID and if on Windows also PSK).
* set time zone and keyboard layout

  Related to feature requests/issues:
  Ref #127
  Ref #86
  Ref #102
  Ref #73
  Ref #68
  Ref #25
  Ref #12

- Option Window also allows setting a couple other general settings:

* Adds option for audible notification (beep) when imaging completes.
  Closes #46

* Adds option not to eject media when done.
  Closes #144

- No longer suspends a number of Windows services during Imaging
  (We want Windows to detect the drive and mount it, or we may
   not be able to alter files on FAT partition).
This commit is contained in:
Floris Bos 2021-01-17 17:43:17 +01:00
parent 2844b5bd1a
commit 86f893388c
17 changed files with 2563 additions and 34 deletions

View file

@ -6,9 +6,9 @@ if (APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "" FORCE)
endif()
project(rpi-imager LANGUAGES CXX)
project(rpi-imager LANGUAGES CXX C)
set(IMAGER_VERSION_MAJOR 1)
set(IMAGER_VERSION_MINOR 5)
set(IMAGER_VERSION_MINOR 6)
set(IMAGER_VERSION_STR "${IMAGER_VERSION_MAJOR}.${IMAGER_VERSION_MINOR}")
set(IMAGER_VERSION_CSV "${IMAGER_VERSION_MAJOR},${IMAGER_VERSION_MINOR},0,0")
add_definitions(-DIMAGER_VERSION_STR="${IMAGER_VERSION_STR}")
@ -20,7 +20,7 @@ set(CMAKE_AUTORCC ON)
# Adding headers explicity so they are displayed in Qt Creator
set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h drivelistmodelpollthread.h driveformatthread.h powersaveblocker.h
downloadthread.h downloadextractthread.h localfileextractthread.h downloadstatstelemetry.h dependencies/mountutils/src/mountutils.hpp)
downloadthread.h downloadextractthread.h localfileextractthread.h downloadstatstelemetry.h dependencies/mountutils/src/mountutils.hpp dependencies/sha256crypt/sha256crypt.h)
# Add dependencies
if (APPLE)
@ -43,7 +43,7 @@ elseif (WIN32)
windows/winfile.cpp windows/winfile.h
windows/rpi-imager.rc)
find_package(Qt5WinExtras REQUIRED)
set(EXTRALIBS setupapi Qt5::WinExtras)
set(EXTRALIBS setupapi wlanapi Qt5::WinExtras)
endif()
include_directories(BEFORE .)
@ -69,7 +69,7 @@ endif()
set(SOURCES "main.cpp" "imagewriter.cpp" "networkaccessmanagerfactory.cpp"
"drivelistitem.cpp" "drivelistmodel.cpp" "drivelistmodelpollthread.cpp" "downloadthread.cpp" "downloadextractthread.cpp"
"driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc")
"driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc" "dependencies/sha256crypt/sha256crypt.c")
find_package(Qt5 COMPONENTS Core Quick LinguistTools Svg OPTIONAL_COMPONENTS Widgets)
if (Qt5Widgets_FOUND)

473
OptionsPopup.qml Normal file
View file

@ -0,0 +1,473 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2021 Raspberry Pi (Trading) Limited
*/
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.0
import QtQuick.Controls.Material 2.2
Popup {
id: popup
//x: 62
x: (parent.width-width)/2
y: 10
//width: parent.width-125
width: popupbody.implicitWidth+60
height: parent.height-20
padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
property bool initialized: false
property string config
property string cmdline
property string firstrun
// background of title
Rectangle {
color: "#f5f5f5"
anchors.right: parent.right
anchors.top: parent.top
height: 35
width: parent.width
}
// line under title
Rectangle {
color: "#afafaf"
width: parent.width
y: 35
implicitHeight: 1
}
Text {
id: msgx
text: "X"
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 25
anchors.topMargin: 10
font.family: roboto.name
font.bold: true
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
popup.close()
}
}
}
ColumnLayout {
spacing: 20
anchors.fill: parent
Text {
id: popupheader
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true
Layout.topMargin: 10
font.family: roboto.name
font.bold: true
text: qsTr("Advanced options")
}
ScrollView {
id: popupbody
font.family: roboto.name
//Layout.maximumWidth: popup.width-30
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 25
Layout.topMargin: 10
clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ColumnLayout {
GroupBox {
title: qsTr("Image customization options (for this session only)")
Layout.fillWidth: true
ColumnLayout {
spacing: -10
CheckBox {
id: chkOverscan
text: qsTr("Disable overscan")
}
CheckBox {
id: chkHostname
text: qsTr("Set hostname")
onCheckedChanged: {
if (checked) {
fieldHostname.forceActiveFocus()
}
}
}
RowLayout {
enabled: chkHostname.checked
Layout.leftMargin: 40
TextField {
id: fieldHostname
text: "raspberrypi"
}
Text {
text : ".local"
color: parent.enabled ? "black" : "grey"
}
}
CheckBox {
id: chkSSH
text: qsTr("Enable SSH")
onCheckedChanged: {
if (checked) {
if (!radioPasswordAuthentication.checked && !radioPubKeyAuthentication.checked) {
radioPasswordAuthentication.checked = true
}
if (radioPasswordAuthentication.checked && !fieldUserPassword.length) {
fieldUserPassword.forceActiveFocus()
}
}
}
}
ColumnLayout {
enabled: chkSSH.checked
Layout.leftMargin: 40
spacing: -5
RadioButton {
id: radioPasswordAuthentication
text: qsTr("Use password authentication")
onCheckedChanged: {
if (checked) {
fieldUserPassword.forceActiveFocus()
}
}
}
GridLayout {
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
enabled: radioPasswordAuthentication.checked
Text {
text: qsTr("Set password for 'pi' user:")
color: parent.enabled ? "black" : "grey"
}
TextField {
id: fieldUserPassword
echoMode: TextInput.Password
Layout.minimumWidth: 200
}
}
RadioButton {
id: radioPubKeyAuthentication
text: qsTr("Allow public-key authentication only")
onCheckedChanged: {
if (checked) {
fieldPublicKey.forceActiveFocus()
}
}
}
GridLayout {
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
enabled: radioPubKeyAuthentication.checked
Text {
text: qsTr("Set authorized_keys for 'pi':")
color: parent.enabled ? "black" : "grey"
}
TextField {
id: fieldPublicKey
Layout.minimumWidth: 200
}
}
}
CheckBox {
id: chkWifi
text: qsTr("Configure wifi")
onCheckedChanged: {
if (checked) {
if (!fieldWifiSSID.length) {
fieldWifiSSID.forceActiveFocus()
} else if (!fieldWifiPassword.length) {
fieldWifiPassword.forceActiveFocus()
}
}
}
}
GridLayout {
enabled: chkWifi.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("SSID:")
color: parent.enabled ? "black" : "grey"
}
TextField {
id: fieldWifiSSID
Layout.minimumWidth: 200
}
Text {
text: qsTr("Password:")
color: parent.enabled ? "black" : "grey"
}
TextField {
id: fieldWifiPassword
Layout.minimumWidth: 200
echoMode: chkShowPassword.checked ? TextInput.Normal : TextInput.Password
}
CheckBox {
id: chkShowPassword
Layout.columnSpan: 2
text: qsTr("Show password")
checked: true
}
Text {
text: qsTr("Wifi country:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldWifiCountry
editable: true
}
}
CheckBox {
id: chkLocale
text: qsTr("Set locale settings")
}
GridLayout {
enabled: chkLocale.checked
Layout.leftMargin: 40
columns: 2
columnSpacing: 10
rowSpacing: -5
Text {
text: qsTr("Time zone:")
color: parent.enabled ? "black" : "grey"
}
ComboBox {
id: fieldTimezone
editable: true
Layout.minimumWidth: 200
}
Text {
text: qsTr("Keyboard layout:")
color: parent.enabled ? "black" : "grey"
}
TextField {
id: fieldKeyboardLayout
Layout.minimumWidth: 200
text: "us"
}
CheckBox {
id: chkSkipFirstUse
text: qsTr("Skip first-run wizard")
}
}
}
}
GroupBox {
title: qsTr("Persistent settings")
Layout.fillWidth: true
ColumnLayout {
spacing: -10
CheckBox {
id: chkBeep
text: qsTr("Play sound when finished")
}
CheckBox {
id: chkEject
text: qsTr("Eject media when finished")
}
CheckBox {
id: chkTelemtry
text: qsTr("Enable telemetry")
}
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignCenter | Qt.AlignBottom
Layout.bottomMargin: 10
spacing: 20
Button {
text: qsTr("SAVE")
onClicked: {
if (radioPasswordAuthentication.checked && fieldUserPassword.text.length == 0)
{
fieldUserPassword.forceActiveFocus()
return
}
applySettings()
popup.close()
}
Material.foreground: "#ffffff"
Material.background: "#c51a4a"
font.family: roboto.name
Accessible.onPressAction: clicked()
}
Text { text: " " }
}
}
function openPopup() {
if (!initialized) {
chkBeep.checked = imageWriter.getBoolSetting("beep")
chkTelemtry.checked = imageWriter.getBoolSetting("telemetry")
chkEject.checked = imageWriter.getBoolSetting("eject")
fieldTimezone.model = imageWriter.getTimezoneList()
fieldPublicKey.text = imageWriter.getDefaultPubKey()
fieldWifiCountry.model = imageWriter.getCountryList()
fieldWifiCountry.currentIndex = fieldWifiCountry.find("GB")
fieldWifiSSID.text = imageWriter.getSSID()
if (fieldWifiSSID.text.length) {
fieldWifiPassword.text = imageWriter.getPSK(fieldWifiSSID.text)
if (fieldWifiPassword.text.length) {
chkShowPassword.checked = false
}
}
var tz = imageWriter.getTimezone()
var tzidx = fieldTimezone.find(tz)
if (tzidx === -1) {
fieldTimezone.editText = tz
} else {
fieldTimezone.currentIndex = tzidx
}
/* Lacking an easy cross-platform to fetch keyboard layout
from host system, just default to "gb" for people in
UK time zone for now, and "us" for everyone else */
if (tz == "Europe/London") {
fieldKeyboardLayout.text = "gb"
}
initialized = true
}
open()
}
function addCmdline(s) {
cmdline += " "+s
}
function addConfig(s) {
config += s+"\n"
}
function addFirstRun(s) {
firstrun += s+"\n"
}
function escapeshellarg(arg) {
return "'"+arg.replace(/'/g, "\\'")+"'"
}
function applySettings()
{
cmdline = ""
config = ""
firstrun = ""
if (chkOverscan.checked) {
addConfig("disable_overscan=1")
}
if (chkHostname.checked && fieldHostname.length) {
addFirstRun("CURRENT_HOSTNAME=`cat /etc/hostname | tr -d \" \\t\\n\\r\"`")
addFirstRun("echo "+fieldHostname.text+" >/etc/hostname")
addFirstRun("sed -i \"s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\\t"+fieldHostname.text+"/g\" /etc/hosts")
}
if (chkSSH.checked) {
// First user may not be called 'pi' on all distributions, so look username up
addFirstRun("FIRSTUSER=`getent passwd 1000 | cut -d: -f1`");
addFirstRun("FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`")
if (radioPasswordAuthentication.checked) {
addFirstRun("echo \"$FIRSTUSER:\""+escapeshellarg(imageWriter.crypt(fieldUserPassword.text))+" | chpasswd -e")
}
if (radioPubKeyAuthentication.checked) {
var pubkey = fieldPublicKey.text.replace(/\n/g, "")
if (pubkey.length) {
addFirstRun("install -o \"$FIRSTUSER\" -m 700 -d \"$FIRSTUSERHOME/.ssh\"")
addFirstRun("install -o \"$FIRSTUSER\" -m 600 <(echo \""+pubkey+"\") \"$FIRSTUSERHOME/.ssh/authorized_keys\"")
}
addFirstRun("echo 'PasswordAuthentication no' >>/etc/ssh/sshd_config")
}
addFirstRun("systemctl enable ssh")
}
if (chkWifi.checked) {
var wpaconfig = "country="+fieldWifiCountry.editText+"\n"
wpaconfig += "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n"
wpaconfig += "ap_scan=1\n\n"
wpaconfig += "update_config=1\n"
wpaconfig += "network={\n"
wpaconfig += "\tssid=\""+fieldWifiSSID.text+"\"\n"
wpaconfig += "\tpsk=\""+fieldWifiPassword.text+"\"\n"
wpaconfig += "}\n"
addFirstRun("cat >/etc/wpa_supplicant/wpa_supplicant.conf <<WPAEOF")
addFirstRun(wpaconfig)
addFirstRun("WPAEOF")
addFirstRun("chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf")
addFirstRun("rfkill unblock wifi")
addFirstRun("for filename in /var/lib/systemd/rfkill/*:wlan ; do")
addFirstRun(" echo 0 > $filename")
addFirstRun("done")
}
if (chkLocale.checked) {
if (chkSkipFirstUse) {
addFirstRun("rm -f /etc/xdg/autostart/piwiz.desktop")
}
addFirstRun("rm -f /etc/localtime")
addFirstRun("echo \""+fieldTimezone.editText+"\" >/etc/timezone")
addFirstRun("dpkg-reconfigure -f noninteractive tzdata")
addFirstRun("cat >/etc/default/keyboard <<KBEOF")
addFirstRun("XKBMODEL=\"pc105\"")
addFirstRun("XKBLAYOUT=\""+fieldKeyboardLayout.text+"\"")
addFirstRun("XKBVARIANT=\"\"")
addFirstRun("XKBOPTIONS=\"\"")
addFirstRun("KBEOF")
addFirstRun("dpkg-reconfigure -f noninteractive keyboard-configuration")
}
if (firstrun.length) {
firstrun = "#!/bin/bash\n\n"+"set +e\n\n"+firstrun
addFirstRun("rm -f /boot/firstrun.sh")
addFirstRun("sed -i 's| systemd.run.*||g' /boot/cmdline.txt")
addFirstRun("exit 0")
/* using systemd.run_success_action=none does not seem to have desired effect
systemd then stays at "reached target kernel command line", so use reboot instead */
addCmdline("systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target")
}
imageWriter.setImageCustomization(config, cmdline, firstrun)
imageWriter.setSetting("beep", chkBeep.checked)
imageWriter.setSetting("eject", chkEject.checked)
imageWriter.setSetting("telemetry", chkTelemtry.checked)
}
}

249
countries.txt Normal file
View file

@ -0,0 +1,249 @@
AD
AE
AF
AG
AI
AL
AM
AO
AQ
AR
AS
AT
AU
AW
AX
AZ
BA
BB
BD
BE
BF
BG
BH
BI
BJ
BL
BM
BN
BO
BQ
BR
BS
BT
BV
BW
BY
BZ
CA
CC
CD
CF
CG
CH
CI
CK
CL
CM
CN
CO
CR
CU
CV
CW
CX
CY
CZ
DE
DJ
DK
DM
DO
DZ
EC
EE
EG
EH
ER
ES
ET
FI
FJ
FK
FM
FO
FR
GA
GB
GD
GE
GF
GG
GH
GI
GL
GM
GN
GP
GQ
GR
GS
GT
GU
GW
GY
HK
HM
HN
HR
HT
HU
ID
IE
IL
IM
IN
IO
IQ
IR
IS
IT
JE
JM
JO
JP
KE
KG
KH
KI
KM
KN
KP
KR
KW
KY
KZ
LA
LB
LC
LI
LK
LR
LS
LT
LU
LV
LY
MA
MC
MD
ME
MF
MG
MH
MK
ML
MM
MN
MO
MP
MQ
MR
MS
MT
MU
MV
MW
MX
MY
MZ
NA
NC
NE
NF
NG
NI
NL
NO
NP
NR
NU
NZ
OM
PA
PE
PF
PG
PH
PK
PL
PM
PN
PR
PS
PT
PW
PY
QA
RE
RO
RS
RU
RW
SA
SB
SC
SD
SE
SG
SH
SI
SJ
SK
SL
SM
SN
SO
SR
SS
ST
SV
SX
SY
SZ
TC
TD
TF
TG
TH
TJ
TK
TL
TM
TN
TO
TR
TT
TV
TW
TZ
UA
UG
UM
US
UY
UZ
VA
VC
VE
VG
VI
VN
VU
WF
WS
YE
YT
ZA
ZM
ZW

7
debian/changelog vendored
View file

@ -1,3 +1,10 @@
rpi-imager (1.6) unstable; urgency=medium
* Wayland: fix "Client tried to set invalid geometry" error
* Add advanced users option screen available under SHIFT-CTRL-X
-- Floris Bos <bos@je-eigen-domein.nl> Fri, 15 Jan 2021 22:00:21 +0100
rpi-imager (1.5) unstable; urgency=medium
* More verbose progress/error reporting

741
dependencies/sha256crypt/sha256crypt.c vendored Normal file
View file

@ -0,0 +1,741 @@
/* SHA256-based Unix crypt implementation.
Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>. */
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#ifdef __APPLE__
#include <machine/endian.h>
#else
#ifdef _WIN32
#define __LITTLE_ENDIAN 1234
#define __BYTE_ORDER __LITTLE_ENDIAN
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
char *stpncpy(char *dest, const char *src, size_t n)
{
size_t size = strnlen(src, n);
memcpy(dest, src, size);
dest += size;
if (size != n)
dest[0] = '\0';
return dest;
}
#else
#include <endian.h>
#endif
#endif
/* Structure to save state of computation between the single steps. */
struct sha256_ctx
{
uint32_t H[8];
uint32_t total[2];
uint32_t buflen;
char buffer[128]; /* NB: always correctly aligned for uint32_t. */
};
#if __BYTE_ORDER == __LITTLE_ENDIAN
# define SWAP(n) \
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
#else
# define SWAP(n) (n)
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. (FIPS 180-2:5.1.1) */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
/* Constants for SHA256 from FIPS 180-2:4.2.2. */
static const uint32_t K[64] =
{
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0. */
static void
sha256_process_block (const void *buffer, size_t len, struct sha256_ctx *ctx)
{
const uint32_t *words = buffer;
size_t nwords = len / sizeof (uint32_t);
uint32_t a = ctx->H[0];
uint32_t b = ctx->H[1];
uint32_t c = ctx->H[2];
uint32_t d = ctx->H[3];
uint32_t e = ctx->H[4];
uint32_t f = ctx->H[5];
uint32_t g = ctx->H[6];
uint32_t h = ctx->H[7];
/* First increment the byte count. FIPS 180-2 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] += len;
if (ctx->total[0] < len)
++ctx->total[1];
/* Process all bytes in the buffer with 64 bytes in each round of
the loop. */
while (nwords > 0)
{
uint32_t W[64];
uint32_t a_save = a;
uint32_t b_save = b;
uint32_t c_save = c;
uint32_t d_save = d;
uint32_t e_save = e;
uint32_t f_save = f;
uint32_t g_save = g;
uint32_t h_save = h;
/* Operators defined in FIPS 180-2:4.1.2. */
#define Ch(x, y, z) ((x & y) ^ (~x & z))
#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
/* It is unfortunate that C does not provide an operator for
cyclic rotation. Hope the C compiler is smart enough. */
#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
/* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
for (unsigned int t = 0; t < 16; ++t)
{
W[t] = SWAP (*words);
++words;
}
for (unsigned int t = 16; t < 64; ++t)
W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
/* The actual computation according to FIPS 180-2:6.2.2 step 3. */
for (unsigned int t = 0; t < 64; ++t)
{
uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
uint32_t T2 = S0 (a) + Maj (a, b, c);
h = g;
g = f;
f = e;
e = d + T1;
d = c;
c = b;
b = a;
a = T1 + T2;
}
/* Add the starting values of the context according to FIPS 180-2:6.2.2
step 4. */
a += a_save;
b += b_save;
c += c_save;
d += d_save;
e += e_save;
f += f_save;
g += g_save;
h += h_save;
/* Prepare for the next round. */
nwords -= 16;
}
/* Put checksum in context given as argument. */
ctx->H[0] = a;
ctx->H[1] = b;
ctx->H[2] = c;
ctx->H[3] = d;
ctx->H[4] = e;
ctx->H[5] = f;
ctx->H[6] = g;
ctx->H[7] = h;
}
/* Initialize structure containing state of computation.
(FIPS 180-2:5.3.2) */
static void
sha256_init_ctx (struct sha256_ctx *ctx)
{
ctx->H[0] = 0x6a09e667;
ctx->H[1] = 0xbb67ae85;
ctx->H[2] = 0x3c6ef372;
ctx->H[3] = 0xa54ff53a;
ctx->H[4] = 0x510e527f;
ctx->H[5] = 0x9b05688c;
ctx->H[6] = 0x1f83d9ab;
ctx->H[7] = 0x5be0cd19;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
static void *
sha256_finish_ctx (struct sha256_ctx *ctx, void *resbuf)
{
/* Take yet unprocessed bytes into account. */
uint32_t bytes = ctx->buflen;
size_t pad;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if (ctx->total[0] < bytes)
++ctx->total[1];
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
memcpy (&ctx->buffer[bytes], fillbuf, pad);
/* Put the 64-bit file length in *bits* at the end of the buffer. */
*(uint32_t *) &ctx->buffer[bytes + pad + 4] = SWAP (ctx->total[0] << 3);
*(uint32_t *) &ctx->buffer[bytes + pad] = SWAP ((ctx->total[1] << 3) |
(ctx->total[0] >> 29));
/* Process last bytes. */
sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
/* Put result from CTX in first 32 bytes following RESBUF. */
for (unsigned int i = 0; i < 8; ++i)
((uint32_t *) resbuf)[i] = SWAP (ctx->H[i]);
return resbuf;
}
static void
sha256_process_bytes (const void *buffer, size_t len, struct sha256_ctx *ctx)
{
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if (ctx->buflen != 0)
{
size_t left_over = ctx->buflen;
size_t add = 128 - left_over > len ? len : 128 - left_over;
memcpy (&ctx->buffer[left_over], buffer, add);
ctx->buflen += add;
if (ctx->buflen > 64)
{
sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap. */
memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
ctx->buflen);
}
buffer = (const char *) buffer + add;
len -= add;
}
/* Process available complete blocks. */
if (len >= 64)
{
/* To check alignment gcc has an appropriate operator. Other
compilers don't. */
#if __GNUC__ >= 2
# define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__ (uint32_t) != 0)
#else
# define UNALIGNED_P(p) (((uintptr_t) p) % sizeof (uint32_t) != 0)
#endif
if (UNALIGNED_P (buffer))
while (len > 64)
{
sha256_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);
buffer = (const char *) buffer + 64;
len -= 64;
}
else
{
sha256_process_block (buffer, len & ~63, ctx);
buffer = (const char *) buffer + (len & ~63);
len &= 63;
}
}
/* Move remaining bytes into internal buffer. */
if (len > 0)
{
size_t left_over = ctx->buflen;
memcpy (&ctx->buffer[left_over], buffer, len);
left_over += len;
if (left_over >= 64)
{
sha256_process_block (ctx->buffer, 64, ctx);
left_over -= 64;
memcpy (ctx->buffer, &ctx->buffer[64], left_over);
}
ctx->buflen = left_over;
}
}
/* Define our magic string to mark salt for SHA256 "encryption"
replacement. */
static const char sha256_salt_prefix[] = "$5$";
/* Prefix for optional rounds specification. */
static const char sha256_rounds_prefix[] = "rounds=";
/* Maximum salt string length. */
#define SALT_LEN_MAX 16
/* Default number of rounds if not explicitly specified. */
#define ROUNDS_DEFAULT 5000
/* Minimum number of rounds. */
#define ROUNDS_MIN 1000
/* Maximum number of rounds. */
#define ROUNDS_MAX 999999999
/* Table with characters for base64 transformation. */
static const char b64t[64] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static char *
sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
{
unsigned char alt_result[32]
__attribute__ ((__aligned__ (__alignof__ (uint32_t))));
unsigned char temp_result[32]
__attribute__ ((__aligned__ (__alignof__ (uint32_t))));
struct sha256_ctx ctx;
struct sha256_ctx alt_ctx;
size_t salt_len;
size_t key_len;
size_t cnt;
char *cp;
char *copied_key = NULL;
char *copied_salt = NULL;
char *p_bytes;
char *s_bytes;
/* Default number of rounds. */
size_t rounds = ROUNDS_DEFAULT;
bool rounds_custom = false;
/* Find beginning of salt string. The prefix should normally always
be present. Just in case it is not. */
if (strncmp (sha256_salt_prefix, salt, sizeof (sha256_salt_prefix) - 1) == 0)
/* Skip salt prefix. */
salt += sizeof (sha256_salt_prefix) - 1;
if (strncmp (salt, sha256_rounds_prefix, sizeof (sha256_rounds_prefix) - 1)
== 0)
{
const char *num = salt + sizeof (sha256_rounds_prefix) - 1;
char *endp;
unsigned long int srounds = strtoul (num, &endp, 10);
if (*endp == '$')
{
salt = endp + 1;
rounds = MAX (ROUNDS_MIN, MIN (srounds, ROUNDS_MAX));
rounds_custom = true;
}
}
salt_len = MIN (strcspn (salt, "$"), SALT_LEN_MAX);
key_len = strlen (key);
if ((key - (char *) 0) % __alignof__ (uint32_t) != 0)
{
char *tmp = (char *) alloca (key_len + __alignof__ (uint32_t));
key = copied_key =
memcpy (tmp + __alignof__ (uint32_t)
- (tmp - (char *) 0) % __alignof__ (uint32_t),
key, key_len);
}
if ((salt - (char *) 0) % __alignof__ (uint32_t) != 0)
{
char *tmp = (char *) alloca (salt_len + __alignof__ (uint32_t));
salt = copied_salt =
memcpy (tmp + __alignof__ (uint32_t)
- (tmp - (char *) 0) % __alignof__ (uint32_t),
salt, salt_len);
}
/* Prepare for the real work. */
sha256_init_ctx (&ctx);
/* Add the key string. */
sha256_process_bytes (key, key_len, &ctx);
/* The last part is the salt string. This must be at most 16
characters and it ends at the first `$' character (for
compatibility with existing implementations). */
sha256_process_bytes (salt, salt_len, &ctx);
/* Compute alternate SHA256 sum with input KEY, SALT, and KEY. The
final result will be added to the first context. */
sha256_init_ctx (&alt_ctx);
/* Add key. */
sha256_process_bytes (key, key_len, &alt_ctx);
/* Add salt. */
sha256_process_bytes (salt, salt_len, &alt_ctx);
/* Add key again. */
sha256_process_bytes (key, key_len, &alt_ctx);
/* Now get result of this (32 bytes) and add it to the other
context. */
sha256_finish_ctx (&alt_ctx, alt_result);
/* Add for any character in the key one byte of the alternate sum. */
for (cnt = key_len; cnt > 32; cnt -= 32)
sha256_process_bytes (alt_result, 32, &ctx);
sha256_process_bytes (alt_result, cnt, &ctx);
/* Take the binary representation of the length of the key and for every
1 add the alternate sum, for every 0 the key. */
for (cnt = key_len; cnt > 0; cnt >>= 1)
if ((cnt & 1) != 0)
sha256_process_bytes (alt_result, 32, &ctx);
else
sha256_process_bytes (key, key_len, &ctx);
/* Create intermediate result. */
sha256_finish_ctx (&ctx, alt_result);
/* Start computation of P byte sequence. */
sha256_init_ctx (&alt_ctx);
/* For every character in the password add the entire password. */
for (cnt = 0; cnt < key_len; ++cnt)
sha256_process_bytes (key, key_len, &alt_ctx);
/* Finish the digest. */
sha256_finish_ctx (&alt_ctx, temp_result);
/* Create byte sequence P. */
cp = p_bytes = alloca (key_len);
for (cnt = key_len; cnt >= 32; cnt -= 32)
cp = memcpy (cp, temp_result, 32) + 32;
memcpy (cp, temp_result, cnt);
/* Start computation of S byte sequence. */
sha256_init_ctx (&alt_ctx);
/* For every character in the password add the entire password. */
for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
sha256_process_bytes (salt, salt_len, &alt_ctx);
/* Finish the digest. */
sha256_finish_ctx (&alt_ctx, temp_result);
/* Create byte sequence S. */
cp = s_bytes = alloca (salt_len);
for (cnt = salt_len; cnt >= 32; cnt -= 32)
cp = memcpy (cp, temp_result, 32) + 32;
memcpy (cp, temp_result, cnt);
/* Repeatedly run the collected hash value through SHA256 to burn
CPU cycles. */
for (cnt = 0; cnt < rounds; ++cnt)
{
/* New context. */
sha256_init_ctx (&ctx);
/* Add key or last result. */
if ((cnt & 1) != 0)
sha256_process_bytes (p_bytes, key_len, &ctx);
else
sha256_process_bytes (alt_result, 32, &ctx);
/* Add salt for numbers not divisible by 3. */
if (cnt % 3 != 0)
sha256_process_bytes (s_bytes, salt_len, &ctx);
/* Add key for numbers not divisible by 7. */
if (cnt % 7 != 0)
sha256_process_bytes (p_bytes, key_len, &ctx);
/* Add key or last result. */
if ((cnt & 1) != 0)
sha256_process_bytes (alt_result, 32, &ctx);
else
sha256_process_bytes (p_bytes, key_len, &ctx);
/* Create intermediate result. */
sha256_finish_ctx (&ctx, alt_result);
}
/* Now we can construct the result string. It consists of three
parts. */
cp = stpncpy (buffer, sha256_salt_prefix, MAX (0, buflen));
buflen -= sizeof (sha256_salt_prefix) - 1;
if (rounds_custom)
{
int n = snprintf (cp, MAX (0, buflen), "%s%zu$",
sha256_rounds_prefix, rounds);
cp += n;
buflen -= n;
}
cp = stpncpy (cp, salt, MIN ((size_t) MAX (0, buflen), salt_len));
buflen -= MIN ((size_t) MAX (0, buflen), salt_len);
if (buflen > 0)
{
*cp++ = '$';
--buflen;
}
#define b64_from_24bit(B2, B1, B0, N) \
do { \
unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
int n = (N); \
while (n-- > 0 && buflen > 0) \
{ \
*cp++ = b64t[w & 0x3f]; \
--buflen; \
w >>= 6; \
} \
} while (0)
b64_from_24bit (alt_result[0], alt_result[10], alt_result[20], 4);
b64_from_24bit (alt_result[21], alt_result[1], alt_result[11], 4);
b64_from_24bit (alt_result[12], alt_result[22], alt_result[2], 4);
b64_from_24bit (alt_result[3], alt_result[13], alt_result[23], 4);
b64_from_24bit (alt_result[24], alt_result[4], alt_result[14], 4);
b64_from_24bit (alt_result[15], alt_result[25], alt_result[5], 4);
b64_from_24bit (alt_result[6], alt_result[16], alt_result[26], 4);
b64_from_24bit (alt_result[27], alt_result[7], alt_result[17], 4);
b64_from_24bit (alt_result[18], alt_result[28], alt_result[8], 4);
b64_from_24bit (alt_result[9], alt_result[19], alt_result[29], 4);
b64_from_24bit (0, alt_result[31], alt_result[30], 3);
if (buflen <= 0)
{
errno = ERANGE;
buffer = NULL;
}
else
*cp = '\0'; /* Terminate the string. */
/* Clear the buffer for the intermediate result so that people
attaching to processes or reading core dumps cannot get any
information. We do it in this way to clear correct_words[]
inside the SHA256 implementation as well. */
sha256_init_ctx (&ctx);
sha256_finish_ctx (&ctx, alt_result);
memset (temp_result, '\0', sizeof (temp_result));
memset (p_bytes, '\0', key_len);
memset (s_bytes, '\0', salt_len);
memset (&ctx, '\0', sizeof (ctx));
memset (&alt_ctx, '\0', sizeof (alt_ctx));
if (copied_key != NULL)
memset (copied_key, '\0', key_len);
if (copied_salt != NULL)
memset (copied_salt, '\0', salt_len);
return buffer;
}
/* This entry point is equivalent to the `crypt' function in Unix
libcs. */
char *
sha256_crypt (const char *key, const char *salt)
{
/* We don't want to have an arbitrary limit in the size of the
password. We can compute an upper bound for the size of the
result in advance and so we can prepare the buffer we pass to
`sha256_crypt_r'. */
static char *buffer;
static int buflen;
int needed = (sizeof (sha256_salt_prefix) - 1
+ sizeof (sha256_rounds_prefix) + 9 + 1
+ strlen (salt) + 1 + 43 + 1);
if (buflen < needed)
{
char *new_buffer = (char *) realloc (buffer, needed);
if (new_buffer == NULL)
return NULL;
buffer = new_buffer;
buflen = needed;
}
return sha256_crypt_r (key, salt, buffer, buflen);
}
#ifdef TEST
static const struct
{
const char *input;
const char result[32];
} tests[] =
{
/* Test vectors from FIPS 180-2: appendix B.1. */
{ "abc",
"\xba\x78\x16\xbf\x8f\x01\xcf\xea\x41\x41\x40\xde\x5d\xae\x22\x23"
"\xb0\x03\x61\xa3\x96\x17\x7a\x9c\xb4\x10\xff\x61\xf2\x00\x15\xad" },
/* Test vectors from FIPS 180-2: appendix B.2. */
{ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
"\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
/* Test vectors from the NESSIE project. */
{ "",
"\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24"
"\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55" },
{ "a",
"\xca\x97\x81\x12\xca\x1b\xbd\xca\xfa\xc2\x31\xb3\x9a\x23\xdc\x4d"
"\xa7\x86\xef\xf8\x14\x7c\x4e\x72\xb9\x80\x77\x85\xaf\xee\x48\xbb" },
{ "message digest",
"\xf7\x84\x6f\x55\xcf\x23\xe1\x4e\xeb\xea\xb5\xb4\xe1\x55\x0c\xad"
"\x5b\x50\x9e\x33\x48\xfb\xc4\xef\xa3\xa1\x41\x3d\x39\x3c\xb6\x50" },
{ "abcdefghijklmnopqrstuvwxyz",
"\x71\xc4\x80\xdf\x93\xd6\xae\x2f\x1e\xfa\xd1\x44\x7c\x66\xc9\x52"
"\x5e\x31\x62\x18\xcf\x51\xfc\x8d\x9e\xd8\x32\xf2\xda\xf1\x8b\x73" },
{ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"\x24\x8d\x6a\x61\xd2\x06\x38\xb8\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
"\xa3\x3c\xe4\x59\x64\xff\x21\x67\xf6\xec\xed\xd4\x19\xdb\x06\xc1" },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"\xdb\x4b\xfc\xbd\x4d\xa0\xcd\x85\xa6\x0c\x3c\x37\xd3\xfb\xd8\x80"
"\x5c\x77\xf1\x5f\xc6\xb1\xfd\xfe\x61\x4e\xe0\xa7\xc8\xfd\xb4\xc0" },
{ "123456789012345678901234567890123456789012345678901234567890"
"12345678901234567890",
"\xf3\x71\xbc\x4a\x31\x1f\x2b\x00\x9e\xef\x95\x2d\xd8\x3c\xa8\x0e"
"\x2b\x60\x02\x6c\x8e\x93\x55\x92\xd0\xf9\xc3\x08\x45\x3c\x81\x3e" }
};
#define ntests (sizeof (tests) / sizeof (tests[0]))
static const struct
{
const char *salt;
const char *input;
const char *expected;
} tests2[] =
{
{ "$5$saltstring", "Hello world!",
"$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" },
{ "$5$rounds=10000$saltstringsaltstring", "Hello world!",
"$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2."
"opqey6IcA" },
{ "$5$rounds=5000$toolongsaltstring", "This is just a test",
"$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8"
"mGRcvxa5" },
{ "$5$rounds=1400$anotherlongsaltstring",
"a very much longer text to encrypt. This one even stretches over more"
"than one line.",
"$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12"
"oP84Bnq1" },
{ "$5$rounds=77777$short",
"we have a short salt string but not a short password",
"$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" },
{ "$5$rounds=123456$asaltof16chars..", "a short string",
"$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/"
"cZKmF/wJvD" },
{ "$5$rounds=10$roundstoolow", "the minimum number is still observed",
"$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97"
"2bIC" },
};
#define ntests2 (sizeof (tests2) / sizeof (tests2[0]))
int
main (void)
{
struct sha256_ctx ctx;
char sum[32];
int result = 0;
int cnt;
for (cnt = 0; cnt < (int) ntests; ++cnt)
{
sha256_init_ctx (&ctx);
sha256_process_bytes (tests[cnt].input, strlen (tests[cnt].input), &ctx);
sha256_finish_ctx (&ctx, sum);
if (memcmp (tests[cnt].result, sum, 32) != 0)
{
printf ("test %d run %d failed\n", cnt, 1);
result = 1;
}
sha256_init_ctx (&ctx);
for (int i = 0; tests[cnt].input[i] != '\0'; ++i)
sha256_process_bytes (&tests[cnt].input[i], 1, &ctx);
sha256_finish_ctx (&ctx, sum);
if (memcmp (tests[cnt].result, sum, 32) != 0)
{
printf ("test %d run %d failed\n", cnt, 2);
result = 1;
}
}
/* Test vector from FIPS 180-2: appendix B.3. */
char buf[1000];
memset (buf, 'a', sizeof (buf));
sha256_init_ctx (&ctx);
for (int i = 0; i < 1000; ++i)
sha256_process_bytes (buf, sizeof (buf), &ctx);
sha256_finish_ctx (&ctx, sum);
static const char expected[32] =
"\xcd\xc7\x6e\x5c\x99\x14\xfb\x92\x81\xa1\xc7\xe2\x84\xd7\x3e\x67"
"\xf1\x80\x9a\x48\xa4\x97\x20\x0e\x04\x6d\x39\xcc\xc7\x11\x2c\xd0";
if (memcmp (expected, sum, 32) != 0)
{
printf ("test %d failed\n", cnt);
result = 1;
}
for (cnt = 0; cnt < ntests2; ++cnt)
{
char *cp = sha256_crypt (tests2[cnt].input, tests2[cnt].salt);
if (strcmp (cp, tests2[cnt].expected) != 0)
{
printf ("test %d: expected \"%s\", got \"%s\"\n",
cnt, tests2[cnt].expected, cp);
result = 1;
}
}
if (result == 0)
puts ("all tests OK");
return result;
}
#endif

11
dependencies/sha256crypt/sha256crypt.h vendored Normal file
View file

@ -0,0 +1,11 @@
#ifndef SHA256CRYPT_H
#define SHA256CRYPT_H
extern "C" {
char *
sha256_crypt (const char *key, const char *salt);
}
#endif // SHA256CRYPT_H

View file

@ -40,6 +40,9 @@ DownloadThread::DownloadThread(const QByteArray &url, const QByteArray &localfil
if (!_curlCount)
curl_global_init(CURL_GLOBAL_DEFAULT);
_curlCount++;
QSettings settings;
_ejectEnabled = settings.value("eject", true).toBool();
}
DownloadThread::~DownloadThread()
@ -124,15 +127,15 @@ bool DownloadThread::_openAndPrepareDevice()
if (std::regex_match(_filename.constData(), m, windriveregex))
{
QByteArray nr = QByteArray::fromStdString(m[1]);
QByteArray _nr = QByteArray::fromStdString(m[1]);
if (!nr.isEmpty()) {
qDebug() << "Removing partition table from Windows drive #" << nr << "(" << _filename << ")";
if (!_nr.isEmpty()) {
qDebug() << "Removing partition table from Windows drive #" << _nr << "(" << _filename << ")";
QProcess proc;
proc.start("diskpart");
proc.waitForStarted();
proc.write("select disk "+nr+"\r\n"
proc.write("select disk "+_nr+"\r\n"
"clean\r\n"
"rescan\r\n");
proc.closeWriteChannel();
@ -665,14 +668,6 @@ void DownloadThread::_writeComplete()
emit finalizing();
#ifdef Q_OS_WIN
// Temporarily stop storage services to prevent \[System Volume Information]\WPSettings.dat being created
QProcess p1;
QStringList args = {"stop", "StorSvc"};
qDebug() << "Stopping storage services";
p1.execute("net", args);
#endif
if (_firstBlock)
{
qDebug() << "Writing first block (which we skipped at first)";
@ -696,14 +691,18 @@ void DownloadThread::_writeComplete()
QThread::sleep(1);
_filename.replace("/dev/rdisk", "/dev/disk");
#endif
eject_disk(_filename.constData());
#ifdef Q_OS_WIN
QStringList args2 = {"start", "StorSvc"};
QProcess *p2 = new QProcess(this);
qDebug() << "Restarting storage services";
p2->startDetached("net", args2);
#endif
if (_ejectEnabled && _config.isEmpty() && _cmdline.isEmpty())
eject_disk(_filename.constData());
if (!_config.isEmpty() || !_cmdline.isEmpty())
{
if (!_customizeImage())
return;
if (_ejectEnabled)
eject_disk(_filename.constData());
}
emit success();
}
@ -797,3 +796,201 @@ qint64 DownloadThread::_sectorsWritten()
#endif
return -1;
}
void DownloadThread::setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun)
{
_config = config;
_cmdline = cmdline;
_firstrun = firstrun;
}
bool DownloadThread::_customizeImage()
{
QString folder;
QByteArray devlower = _filename.toLower();
emit preparationStatusUpdate(tr("Waiting for FAT partition to be mounted"));
/* See if OS auto-mounted the device */
for (int tries = 0; tries < 3; tries++)
{
QThread::sleep(1);
auto l = Drivelist::ListStorageDevices();
for (auto i : l)
{
if (QByteArray::fromStdString(i.device).toLower() == devlower && i.mountpoints.size())
{
folder = QByteArray::fromStdString(i.mountpoints.front());
break;
}
}
}
#ifdef Q_OS_LINUX
bool manualmount = false;
if (folder.isEmpty())
{
/* Manually mount folder */
manualmount = true;
QByteArray fatpartition = _filename;
if (isdigit(fatpartition.at(fatpartition.length()-1)))
fatpartition += "p1";
else
fatpartition += "1";
if (::access(devlower.constData(), W_OK) != 0)
{
/* Not running as root, try to outsource mounting to udisks2 */
#ifndef QT_NO_DBUS
UDisks2Api udisks2;
folder = udisks2.mountDevice(fatpartition);
#endif
}
else
{
/* Running as root, attempt running mount directly */
QTemporaryDir td;
QStringList args;
folder = td.path();
args << fatpartition << folder;
if (QProcess::execute("mount", args) != 0)
{
emit error(tr("Error mounting FAT32 partition"));
return false;
}
td.setAutoRemove(false);
}
}
#endif
if (folder.isEmpty())
{
//
qDebug() << "drive info. searching for:" << devlower;
auto l = Drivelist::ListStorageDevices();
for (auto i : l)
{
qDebug() << "drive" << QByteArray::fromStdString(i.device).toLower();
for (auto mp : i.mountpoints) {
qDebug() << "mountpoint:" << QByteArray::fromStdString(mp);
}
}
//
emit error(tr("Operating system did not mount FAT32 partition"));
return false;
}
emit preparationStatusUpdate(tr("Customizing image"));
if (!_firstrun.isEmpty())
{
QFile f(folder+"/firstrun.sh");
if (f.open(f.WriteOnly))
{
f.write(_firstrun);
f.close();
}
else
{
emit error(tr("Error creating firstrun.sh on FAT partition"));
return false;
}
}
if (!_config.isEmpty())
{
auto configItems = _config.split('\n');
configItems.removeAll("");
QByteArray config;
QFile f(folder+"/config.txt");
if (f.exists() && f.open(f.ReadOnly))
{
config = f.readAll();
f.close();
}
for (QByteArray item : configItems)
{
if (config.contains("#"+item)) {
/* Uncomment existing line */
config.replace("#"+item, item);
} else if (config.contains("\n"+item)) {
/* config.txt already contains the line */
} else {
/* Append new line to config.txt */
if (config.right(1) != "\n")
config += "\n"+item+"\n";
else
config += item+"\n";
}
}
if (f.open(f.WriteOnly))
{
f.write(config);
f.close();
}
else
{
emit error(tr("Error writing to config.txt on FAT partition"));
return false;
}
}
if (!_cmdline.isEmpty())
{
QByteArray cmdline;
QFile f(folder+"/cmdline.txt");
if (f.exists() && f.open(f.ReadOnly))
{
cmdline = f.readAll().trimmed();
f.close();
}
cmdline += _cmdline;
if (f.open(f.WriteOnly))
{
f.write(cmdline);
f.close();
}
else
{
emit error(tr("Error writing to cmdline.txt on FAT partition"));
return false;
}
}
emit finalizing();
#ifdef Q_OS_LINUX
if (manualmount)
{
if (::access(devlower.constData(), W_OK) != 0)
{
#ifndef QT_NO_DBUS
UDisks2Api udisks2;
udisks2.unmountDrive(devlower);
#endif
}
else
{
QStringList args;
args << folder;
QProcess::execute("umount", args);
QDir d;
d.rmdir(folder);
}
}
#endif
#ifndef Q_OS_WIN
::sync();
#endif
return true;
}

View file

@ -109,6 +109,11 @@ public:
*/
void setInputBufferSize(int len);
/*
* Enable image customization
*/
void setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun);
/*
* Thread safe download progress query functions
*/
@ -142,6 +147,7 @@ protected:
qint64 _sectorsWritten();
void _closeFiles();
QByteArray _fileGetContentsTrimmed(const QString &filename);
bool _customizeImage();
/*
* libcurl callbacks
@ -158,12 +164,12 @@ protected:
curl_off_t _startOffset;
std::atomic<std::uint64_t> _lastDlTotal, _lastDlNow, _verifyTotal, _lastVerifyNow, _bytesWritten;
qint64 _sectorsStart;
QByteArray _url, _useragent, _buf, _filename, _lastError, _expectedHash;
QByteArray _url, _useragent, _buf, _filename, _lastError, _expectedHash, _config, _cmdline, _firstrun;
char *_firstBlock;
size_t _firstBlockSize;
static QByteArray _proxy;
static int _curlCount;
bool _cancelled, _successful, _verifyEnabled, _cacheEnabled;
bool _cancelled, _successful, _verifyEnabled, _cacheEnabled, _ejectEnabled;
time_t _lastModified, _serverTime, _lastFailureTime;
QElapsedTimer _timer;
int _inputBufferSize;

View file

@ -130,7 +130,7 @@ void DriveFormatThread::run()
#elif defined(Q_OS_LINUX)
if (::access(_device, W_OK) != 0)
if (::access(_device.constData(), W_OK) != 0)
{
/* Not running as root, try to outsource formatting to udisks2 */

View file

@ -7,17 +7,21 @@
#include "drivelistitem.h"
#include "downloadextractthread.h"
#include "dependencies/drivelist/src/drivelist.hpp"
#include "dependencies/sha256crypt/sha256crypt.h"
#include "driveformatthread.h"
#include "localfileextractthread.h"
#include "downloadstatstelemetry.h"
#include <archive.h>
#include <archive_entry.h>
#include <random>
#include <QFileInfo>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QProcess>
#include <QRegExp>
#include <QStandardPaths>
#include <QStorageInfo>
#include <QTimeZone>
#include <QWindow>
#include <QGuiApplication>
#include <QNetworkInterface>
@ -29,11 +33,17 @@
#include <QVersionNumber>
#ifndef QT_NO_WIDGETS
#include <QFileDialog>
#include <QApplication>
#endif
#ifdef Q_OS_WIN
#include <windows.h>
#include <winioctl.h>
#include <wlanapi.h>
#ifndef WLAN_PROFILE_GET_PLAINTEXT_KEY
#define WLAN_PROFILE_GET_PLAINTEXT_KEY 4
#endif
#include <QWinTaskbarButton>
#include <QWinTaskbarProgress>
#endif
@ -55,8 +65,6 @@ ImageWriter::ImageWriter(QObject *parent)
#ifdef Q_OS_WIN
_taskbarButton = nullptr;
QProcess *p = new QProcess(this);
p->start("net stop ShellHWDetection");
#endif
if (!_settings.isWritable() && !_settings.fileName().isEmpty())
@ -104,10 +112,7 @@ ImageWriter::ImageWriter(QObject *parent)
ImageWriter::~ImageWriter()
{
#ifdef Q_OS_WIN
QProcess *p = new QProcess(this);
p->startDetached("net start ShellHWDetection");
#endif
}
void ImageWriter::setEngine(QQmlApplicationEngine *engine)
@ -216,6 +221,7 @@ void ImageWriter::startWrite()
connect(_thread, SIGNAL(preparationStatusUpdate(QString)), SLOT(onPreparationStatusUpdate(QString)));
_thread->setVerifyEnabled(_verifyEnabled);
_thread->setUserAgent(QString("Mozilla/5.0 rpi-imager/%1").arg(constantVersion()).toUtf8());
_thread->setImageCustomization(_config, _cmdline, _firstrun);
if (!_expectedHash.isEmpty() && _cachedFileHash != _expectedHash && _cachingEnabled)
{
@ -453,12 +459,24 @@ void ImageWriter::onSuccess()
{
stopProgressPolling();
emit success();
#ifndef QT_NO_WIDGETS
if (_settings.value("beep").toBool())
{
QApplication::beep();
}
#endif
}
void ImageWriter::onError(QString msg)
{
stopProgressPolling();
emit error(msg);
#ifndef QT_NO_WIDGETS
if (_settings.value("beep").toBool())
QApplication::beep();
#endif
}
void ImageWriter::onFinalizing()
@ -620,6 +638,8 @@ void ImageWriter::onTimeSyncReply(QNetworkReply *reply)
}
reply->deleteLater();
#else
Q_UNUSED(reply)
#endif
}
@ -698,6 +718,214 @@ QByteArray ImageWriter::getUsbSourceOSlist()
#endif
}
QString ImageWriter::getDefaultPubKey()
{
QByteArray pubkey;
QFile pubfile(QDir::homePath()+"/.ssh/id_rsa.pub");
if (pubfile.exists() && pubfile.open(QFile::ReadOnly))
{
pubkey = pubfile.readAll().trimmed();
pubfile.close();
}
return pubkey;
}
QString ImageWriter::getTimezone()
{
return QTimeZone::systemTimeZoneId();
}
QStringList ImageWriter::getTimezoneList()
{
QStringList timezones;
QFile f(":/timezones.txt");
if ( f.open(f.ReadOnly) )
{
timezones = QString(f.readAll()).split('\n');
f.close();
}
return timezones;
}
QStringList ImageWriter::getCountryList()
{
QStringList countries;
QFile f(":/countries.txt");
if ( f.open(f.ReadOnly) )
{
countries = QString(f.readAll()).split('\n');
f.close();
}
return countries;
}
QString ImageWriter::getSSID()
{
/* Qt used to have proper bearer management that was able to provide a list of
SSIDs, but since they retired it, resort to calling platform specific tools for now.
Minimal implementation that only gets the currently connected SSID */
QString program, regexpstr, ssid;
QStringList args;
QProcess proc;
#ifdef Q_OS_WIN
program = "netsh";
args << "wlan" << "show" << "interfaces";
regexpstr = "[ \t]+SSID[ \t]*: (.+)";
#else
#ifdef Q_OS_DARWIN
program = "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport";
args << "-I";
regexpstr = "[ \t]+SSID: (.+)";
#else
program = "iwgetid";
args << "-r";
#endif
#endif
proc.start(program, args);
if (proc.waitForStarted(2000) && proc.waitForFinished(2000))
{
if (regexpstr.isEmpty())
{
ssid = proc.readAll().trimmed();
}
else
{
QRegExp rx(regexpstr);
QList<QByteArray> outputlines = proc.readAll().replace('\r', "").split('\n');
for (QByteArray line : outputlines) {
if (rx.indexIn(line) != -1)
{
ssid = rx.cap(1);
break;
}
}
}
}
return ssid;
}
QString ImageWriter::getPSK(const QString &ssid)
{
#ifdef Q_OS_WIN
/* Windows allows retrieving wifi PSK */
HANDLE h;
DWORD ret = 0;
DWORD supportedVersion = 0;
DWORD clientVersion = 2;
QString psk;
if (WlanOpenHandle(clientVersion, NULL, &supportedVersion, &h) != ERROR_SUCCESS)
return QString();
PWLAN_INTERFACE_INFO_LIST ifList = NULL;
if (WlanEnumInterfaces(h, NULL, &ifList) == ERROR_SUCCESS)
{
for (int i=0; i < ifList->dwNumberOfItems; i++)
{
PWLAN_PROFILE_INFO_LIST profileList = NULL;
if (WlanGetProfileList(h, &ifList->InterfaceInfo[i].InterfaceGuid,
NULL, &profileList) == ERROR_SUCCESS)
{
for (int j=0; j < profileList->dwNumberOfItems; j++)
{
QString s = QString::fromWCharArray(profileList->ProfileInfo[j].strProfileName);
qDebug() << "Enumerating wifi profiles, SSID found:" << s << " looking for:" << ssid;
if (s == ssid) {
DWORD flags = WLAN_PROFILE_GET_PLAINTEXT_KEY;
DWORD access = 0;
DWORD ret = 0;
LPWSTR xmlstr = NULL;
if ( (ret = WlanGetProfile(h, &ifList->InterfaceInfo[i].InterfaceGuid, profileList->ProfileInfo[j].strProfileName,
NULL, &xmlstr, &flags, &access)) == ERROR_SUCCESS && xmlstr)
{
QString xml = QString::fromWCharArray(xmlstr);
qDebug() << "XML wifi profile:" << xml;
QRegExp rx("<keyMaterial>(.+)</keyMaterial>");
if (rx.indexIn(xml) != -1) {
psk = rx.cap(1);
}
WlanFreeMemory(xmlstr);
break;
}
}
}
}
if (profileList) {
WlanFreeMemory(profileList);
}
}
}
if (ifList)
WlanFreeMemory(ifList);
WlanCloseHandle(h, NULL);
return psk;
#else
Q_UNUSED(ssid)
return QString();
#endif
}
bool ImageWriter::getBoolSetting(const QString &key)
{
/* Some keys have defaults */
if (key == "telemetry")
return _settings.value(key, TELEMETRY_ENABLED_DEFAULT).toBool();
else if (key == "eject")
return _settings.value(key, true).toBool();
else
return _settings.value(key).toBool();
}
void ImageWriter::setSetting(const QString &key, const QVariant &value)
{
_settings.setValue(key, value);
_settings.sync();
}
void ImageWriter::setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun)
{
_config = config;
_cmdline = cmdline;
_firstrun = firstrun;
qDebug() << "Custom config.txt entries:" << config;
qDebug() << "Custom cmdline.txt entries:" << cmdline;
qDebug() << "Custom firstuse.sh:" << firstrun;
}
QString ImageWriter::crypt(const QByteArray &password)
{
QByteArray salt = "$5$";
QByteArray saltchars =
"./0123456789ABCDEFGHIJKLMNOPQRST"
"UVWXYZabcdefghijklmnopqrstuvwxyz";
std::mt19937 gen(static_cast<unsigned>(QDateTime::currentMSecsSinceEpoch()));
std::uniform_int_distribution<> uid(0, saltchars.length()-1);
for (int i=0; i<10; i++)
salt += saltchars[uid(gen)];
return sha256_crypt(password.constData(), salt.constData());
}
void MountUtilsLog(std::string msg) {
qDebug() << "mountutils:" << msg.c_str();
}

View file

@ -92,6 +92,20 @@ public:
/* Returns a json formatted list of the OS images found on USB stick */
Q_INVOKABLE QByteArray getUsbSourceOSlist();
/* Functions to collect information from computer running imager to make image customization easier */
Q_INVOKABLE QString getDefaultPubKey();
Q_INVOKABLE QString getTimezone();
Q_INVOKABLE QStringList getTimezoneList();
Q_INVOKABLE QStringList getCountryList();
Q_INVOKABLE QString getSSID();
Q_INVOKABLE QString getPSK(const QString &ssid);
Q_INVOKABLE bool getBoolSetting(const QString &key);
Q_INVOKABLE void setSetting(const QString &key, const QVariant &value);
Q_INVOKABLE void setImageCustomization(const QByteArray &config, const QByteArray &cmdline, const QByteArray &firstrun);
Q_INVOKABLE QString crypt(const QByteArray &password);
signals:
/* We are emiting signals with QVariant as parameters because QML likes it that way */
@ -124,7 +138,7 @@ protected slots:
protected:
QUrl _src, _repo;
QString _dst, _cacheFileName, _parentCategory, _osName;
QByteArray _expectedHash, _cachedFileHash;
QByteArray _expectedHash, _cachedFileHash, _cmdline, _config, _firstrun;
quint64 _downloadLen, _extrLen, _devLen, _dlnow, _verifynow;
DriveListModel _drivelist;
QQmlApplicationEngine *_engine;

View file

@ -30,6 +30,7 @@ This software depends on the following third-party components covered by open so
- Balena.io mountutils (Apache license)
- libarchive (new BSD license)
- liblzma (public domain)
- cryptsha256 (public domain)
- zlib (zlib license)
- libcurl (libcurl license)
- fat32format.exe (GPL license)
@ -1010,6 +1011,13 @@ liblzma license
- liblzma is in the public domain.
==
cryptsha256 license
==
SHA256-based Unix crypt implementation.
Released into the Public Domain by Ulrich Drepper
==
zlib license
==

View file

@ -170,3 +170,40 @@ bool UDisks2Api::formatDrive(const QString &device, bool mountAfterwards)
return true;
}
QString UDisks2Api::mountDevice(const QString &device)
{
QString devpath = _resolveDevice(device);
if (devpath.isEmpty())
return QString();
QDBusInterface filesystem("org.freedesktop.UDisks2", devpath,
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
QVariantMap mountOptions;
for (int attempt = 0; attempt < 10; attempt++)
{
qDebug() << "Mounting partition";
QDBusReply<QString> mp = filesystem.call("Mount", mountOptions);
if (mp.isValid())
{
qDebug() << "Mounted file system at:" << mp;
return mp;
}
QThread::sleep(1);
}
qDebug() << "Failed to mount file system.";
return QString();
}
void UDisks2Api::unmountDrive(const QString &device)
{
QString devpath = _resolveDevice(device);
if (devpath.isEmpty())
return;
_unmountDrive(devpath);
}

View file

@ -16,6 +16,8 @@ public:
explicit UDisks2Api(QObject *parent = nullptr);
int authOpen(const QString &device, const QString &mode = "rw");
bool formatDrive(const QString &device, bool mountAfterwards = true);
QString mountDevice(const QString &device);
void unmountDrive(const QString &device);
protected:
QString _resolveDevice(const QString &device);

View file

@ -43,6 +43,14 @@ ApplicationWindow {
}
}
Shortcut {
sequences: ["Shift+Ctrl+X", "Shift+Meta+X"]
context: Qt.ApplicationShortcut
onActivated: {
optionspopup.openPopup()
}
}
ColumnLayout {
id: bg
spacing: 0
@ -778,6 +786,10 @@ ApplicationWindow {
}
}
OptionsPopup {
id: optionspopup
}
/* Utility functions */
function httpRequest(url, callback) {
var xhr = new XMLHttpRequest();

View file

@ -22,5 +22,8 @@
<file>icons/cat_misc_utility_images.png</file>
<file>icons/cat_media_players.png</file>
<file>icons/cat_emulation_and_games.png</file>
<file>OptionsPopup.qml</file>
<file>countries.txt</file>
<file>timezones.txt</file>
</qresource>
</RCC>

541
timezones.txt Normal file
View file

@ -0,0 +1,541 @@
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau
Africa/Blantyre
Africa/Brazzaville
Africa/Bujumbura
Africa/Cairo
Africa/Casablanca
Africa/Ceuta
Africa/Conakry
Africa/Dakar
Africa/Dar_es_Salaam
Africa/Djibouti
Africa/Douala
Africa/El_Aaiun
Africa/Freetown
Africa/Gaborone
Africa/Harare
Africa/Johannesburg
Africa/Juba
Africa/Kampala
Africa/Khartoum
Africa/Kigali
Africa/Kinshasa
Africa/Lagos
Africa/Libreville
Africa/Lome
Africa/Luanda
Africa/Lubumbashi
Africa/Lusaka
Africa/Malabo
Africa/Maputo
Africa/Maseru
Africa/Mbabane
Africa/Mogadishu
Africa/Monrovia
Africa/Nairobi
Africa/Ndjamena
Africa/Niamey
Africa/Nouakchott
Africa/Ouagadougou
Africa/Porto-Novo
Africa/Sao_Tome
Africa/Timbuktu
Africa/Tripoli
Africa/Tunis
Africa/Windhoek
America/Adak
America/Anchorage
America/Anguilla
America/Antigua
America/Araguaina
America/Argentina
America/Argentina/Buenos_Aires
America/Argentina/Catamarca
America/Argentina/ComodRivadavia
America/Argentina/Cordoba
America/Argentina/Jujuy
America/Argentina/La_Rioja
America/Argentina/Mendoza
America/Argentina/Rio_Gallegos
America/Argentina/Salta
America/Argentina/San_Juan
America/Argentina/San_Luis
America/Argentina/Tucuman
America/Argentina/Ushuaia
America/Aruba
America/Asuncion
America/Atikokan
America/Atka
America/Bahia
America/Bahia_Banderas
America/Barbados
America/Belem
America/Belize
America/Blanc-Sablon
America/Boa_Vista
America/Bogota
America/Boise
America/Buenos_Aires
America/Cambridge_Bay
America/Campo_Grande
America/Cancun
America/Caracas
America/Catamarca
America/Cayenne
America/Cayman
America/Chicago
America/Chihuahua
America/Coral_Harbour
America/Cordoba
America/Costa_Rica
America/Creston
America/Cuiaba
America/Curacao
America/Danmarkshavn
America/Dawson
America/Dawson_Creek
America/Denver
America/Detroit
America/Dominica
America/Edmonton
America/Eirunepe
America/El_Salvador
America/Ensenada
America/Fortaleza
America/Fort_Wayne
America/Glace_Bay
America/Godthab
America/Goose_Bay
America/Grand_Turk
America/Grenada
America/Guadeloupe
America/Guatemala
America/Guayaquil
America/Guyana
America/Halifax
America/Havana
America/Hermosillo
America/Indiana
America/Indiana/Indianapolis
America/Indiana/Knox
America/Indiana/Marengo
America/Indiana/Petersburg
America/Indianapolis
America/Indiana/Tell_City
America/Indiana/Vevay
America/Indiana/Vincennes
America/Indiana/Winamac
America/Inuvik
America/Iqaluit
America/Jamaica
America/Jujuy
America/Juneau
America/Kentucky
America/Kentucky/Louisville
America/Kentucky/Monticello
America/Knox_IN
America/Kralendijk
America/La_Paz
America/Lima
America/Los_Angeles
America/Louisville
America/Lower_Princes
America/Maceio
America/Managua
America/Manaus
America/Marigot
America/Martinique
America/Matamoros
America/Mazatlan
America/Mendoza
America/Menominee
America/Merida
America/Metlakatla
America/Mexico_City
America/Miquelon
America/Moncton
America/Monterrey
America/Montevideo
America/Montreal
America/Montserrat
America/Nassau
America/New_York
America/Nipigon
America/Nome
America/Noronha
America/North_Dakota
America/North_Dakota/Beulah
America/North_Dakota/Center
America/North_Dakota/New_Salem
America/Ojinaga
America/Panama
America/Pangnirtung
America/Paramaribo
America/Phoenix
America/Port-au-Prince
America/Porto_Acre
America/Port_of_Spain
America/Porto_Velho
America/Puerto_Rico
America/Rainy_River
America/Rankin_Inlet
America/Recife
America/Regina
America/Resolute
America/Rio_Branco
America/Rosario
America/Santa_Isabel
America/Santarem
America/Santiago
America/Santo_Domingo
America/Sao_Paulo
America/Scoresbysund
America/Shiprock
America/Sitka
America/St_Barthelemy
America/St_Johns
America/St_Kitts
America/St_Lucia
America/St_Thomas
America/St_Vincent
America/Swift_Current
America/Tegucigalpa
America/Thule
America/Thunder_Bay
America/Tijuana
America/Toronto
America/Tortola
America/Vancouver
America/Virgin
America/Whitehorse
America/Winnipeg
America/Yakutat
America/Yellowknife
Antarctica/Casey
Antarctica/Davis
Antarctica/DumontDUrville
Antarctica/Macquarie
Antarctica/Mawson
Antarctica/McMurdo
Antarctica/Palmer
Antarctica/Rothera
Antarctica/South_Pole
Antarctica/Syowa
Antarctica/Vostok
Arctic/Longyearbyen
Asia/Aden
Asia/Almaty
Asia/Amman
Asia/Anadyr
Asia/Aqtau
Asia/Aqtobe
Asia/Ashgabat
Asia/Ashkhabad
Asia/Baghdad
Asia/Bahrain
Asia/Baku
Asia/Bangkok
Asia/Beirut
Asia/Bishkek
Asia/Brunei
Asia/Calcutta
Asia/Choibalsan
Asia/Chongqing
Asia/Chungking
Asia/Colombo
Asia/Dacca
Asia/Damascus
Asia/Dhaka
Asia/Dili
Asia/Dubai
Asia/Dushanbe
Asia/Gaza
Asia/Harbin
Asia/Hebron
Asia/Ho_Chi_Minh
Asia/Hong_Kong
Asia/Hovd
Asia/Irkutsk
Asia/Istanbul
Asia/Jakarta
Asia/Jayapura
Asia/Jerusalem
Asia/Kabul
Asia/Kamchatka
Asia/Karachi
Asia/Kashgar
Asia/Kathmandu
Asia/Katmandu
Asia/Kolkata
Asia/Krasnoyarsk
Asia/Kuala_Lumpur
Asia/Kuching
Asia/Kuwait
Asia/Macao
Asia/Macau
Asia/Magadan
Asia/Makassar
Asia/Manila
Asia/Muscat
Asia/Nicosia
Asia/Novokuznetsk
Asia/Novosibirsk
Asia/Omsk
Asia/Oral
Asia/Phnom_Penh
Asia/Pontianak
Asia/Pyongyang
Asia/Qatar
Asia/Qyzylorda
Asia/Rangoon
Asia/Riyadh
Asia/Riyadh87
Asia/Riyadh88
Asia/Riyadh89
Asia/Saigon
Asia/Sakhalin
Asia/Samarkand
Asia/Seoul
Asia/Shanghai
Asia/Singapore
Asia/Taipei
Asia/Tashkent
Asia/Tbilisi
Asia/Tehran
Asia/Tel_Aviv
Asia/Thimbu
Asia/Thimphu
Asia/Tokyo
Asia/Ujung_Pandang
Asia/Ulaanbaatar
Asia/Ulan_Bator
Asia/Urumqi
Asia/Vientiane
Asia/Vladivostok
Asia/Yakutsk
Asia/Yekaterinburg
Asia/Yerevan
Atlantic/Azores
Atlantic/Bermuda
Atlantic/Canary
Atlantic/Cape_Verde
Atlantic/Faeroe
Atlantic/Faroe
Atlantic/Jan_Mayen
Atlantic/Madeira
Atlantic/Reykjavik
Atlantic/South_Georgia
Atlantic/Stanley
Atlantic/St_Helena
Australia/ACT
Australia/Adelaide
Australia/Brisbane
Australia/Broken_Hill
Australia/Canberra
Australia/Currie
Australia/Darwin
Australia/Eucla
Australia/Hobart
Australia/LHI
Australia/Lindeman
Australia/Lord_Howe
Australia/Melbourne
Australia/North
Australia/NSW
Australia/Perth
Australia/Queensland
Australia/South
Australia/Sydney
Australia/Tasmania
Australia/Victoria
Australia/West
Australia/Yancowinna
Brazil/Acre
Brazil/DeNoronha
Brazil/East
Brazil/West
Canada/Atlantic
Canada/Central
Canada/Eastern
Canada/East-Saskatchewan
Canada/Mountain
Canada/Newfoundland
Canada/Pacific
Canada/Saskatchewan
Canada/Yukon
Chile/Continental
Chile/EasterIsland
Etc/GMT
Etc/GMT0
Etc/GMT-0
Etc/GMT+0
Etc/GMT-1
Etc/GMT+1
Etc/GMT-10
Etc/GMT+10
Etc/GMT-11
Etc/GMT+11
Etc/GMT-12
Etc/GMT+12
Etc/GMT-13
Etc/GMT-14
Etc/GMT-2
Etc/GMT+2
Etc/GMT-3
Etc/GMT+3
Etc/GMT-4
Etc/GMT+4
Etc/GMT-5
Etc/GMT+5
Etc/GMT-6
Etc/GMT+6
Etc/GMT-7
Etc/GMT+7
Etc/GMT-8
Etc/GMT+8
Etc/GMT-9
Etc/GMT+9
Etc/Greenwich
Etc/UCT
Etc/Universal
Etc/UTC
Etc/Zulu
Europe/Amsterdam
Europe/Andorra
Europe/Athens
Europe/Belfast
Europe/Belgrade
Europe/Berlin
Europe/Bratislava
Europe/Brussels
Europe/Bucharest
Europe/Budapest
Europe/Chisinau
Europe/Copenhagen
Europe/Dublin
Europe/Gibraltar
Europe/Guernsey
Europe/Helsinki
Europe/Isle_of_Man
Europe/Istanbul
Europe/Jersey
Europe/Kaliningrad
Europe/Kiev
Europe/Lisbon
Europe/Ljubljana
Europe/London
Europe/Luxembourg
Europe/Madrid
Europe/Malta
Europe/Mariehamn
Europe/Minsk
Europe/Monaco
Europe/Moscow
Europe/Nicosia
Europe/Oslo
Europe/Paris
Europe/Podgorica
Europe/Prague
Europe/Riga
Europe/Rome
Europe/Samara
Europe/San_Marino
Europe/Sarajevo
Europe/Simferopol
Europe/Skopje
Europe/Sofia
Europe/Stockholm
Europe/Tallinn
Europe/Tirane
Europe/Tiraspol
Europe/Uzhgorod
Europe/Vaduz
Europe/Vatican
Europe/Vienna
Europe/Vilnius
Europe/Volgograd
Europe/Warsaw
Europe/Zagreb
Europe/Zaporozhye
Europe/Zurich
Indian/Antananarivo
Indian/Chagos
Indian/Christmas
Indian/Cocos
Indian/Comoro
Indian/Kerguelen
Indian/Mahe
Indian/Maldives
Indian/Mauritius
Indian/Mayotte
Indian/Reunion
Mexico/BajaNorte
Mexico/BajaSur
Mexico/General
Mideast/Riyadh87
Mideast/Riyadh88
Mideast/Riyadh89
Pacific/Apia
Pacific/Auckland
Pacific/Chatham
Pacific/Chuuk
Pacific/Easter
Pacific/Efate
Pacific/Enderbury
Pacific/Fakaofo
Pacific/Fiji
Pacific/Funafuti
Pacific/Galapagos
Pacific/Gambier
Pacific/Guadalcanal
Pacific/Guam
Pacific/Honolulu
Pacific/Johnston
Pacific/Kiritimati
Pacific/Kosrae
Pacific/Kwajalein
Pacific/Majuro
Pacific/Marquesas
Pacific/Midway
Pacific/Nauru
Pacific/Niue
Pacific/Norfolk
Pacific/Noumea
Pacific/Pago_Pago
Pacific/Palau
Pacific/Pitcairn
Pacific/Pohnpei
Pacific/Ponape
Pacific/Port_Moresby
Pacific/Rarotonga
Pacific/Saipan
Pacific/Samoa
Pacific/Tahiti
Pacific/Tarawa
Pacific/Tongatapu
Pacific/Truk
Pacific/Wake
Pacific/Wallis
Pacific/Yap
US/Alaska
US/Aleutian
US/Arizona
US/Central
US/Eastern
US/East-Indiana
US/Hawaii
US/Indiana-Starke
US/Michigan
US/Mountain
US/Pacific
US/Pacific-New
US/Samoa