mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-18 07:55:21 +01:00
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:
parent
2844b5bd1a
commit
86f893388c
17 changed files with 2563 additions and 34 deletions
|
@ -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
473
OptionsPopup.qml
Normal 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
249
countries.txt
Normal 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
7
debian/changelog
vendored
|
@ -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
741
dependencies/sha256crypt/sha256crypt.c
vendored
Normal 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
11
dependencies/sha256crypt/sha256crypt.h
vendored
Normal 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
|
|
@ -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
|
||||
|
||||
if (_ejectEnabled && _config.isEmpty() && _cmdline.isEmpty())
|
||||
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 (!_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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
240
imagewriter.cpp
240
imagewriter.cpp
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
==
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
12
main.qml
12
main.qml
|
@ -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();
|
||||
|
|
3
qml.qrc
3
qml.qrc
|
@ -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
541
timezones.txt
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue