From dd4508638c7fbb11430be7b0f787a054e8b6e562 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Mon, 26 May 2025 08:04:25 +0200 Subject: [PATCH] v0.12.0 --- CHANGELOG.md | 6 +- UPGRADING.md | 6 +- crates/dav/src/calendar/update.rs | 43 +- .../directory/src/backend/internal/manage.rs | 2 +- crates/store/src/dispatch/store.rs | 2 +- install.sh | 74 +- install_new.sh | 742 ------------------ 7 files changed, 58 insertions(+), 817 deletions(-) delete mode 100644 install_new.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a9f6c65..7bc165dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [0.12.0] - 2025-05-XX +## [0.12.0] - 2025-05-26 This version includes **breaking changes** to the database layout and requires a migration. Please read the [UPGRADING.md](UPGRADING.md) file for more information on how to upgrade from previous versions. @@ -18,7 +18,6 @@ This version includes **breaking changes** to the database layout and requires a - Add `description` property to OIDC ID token (#1234). ### Changed - - Deprecated gossip protocol in favor of the new [coordinator](https://stalw.art/docs/cluster/coordination/overview) options. - Renamed Git repository from `stalwartlabs/mail-server` to `stalwartlabs/stalwart` and the Docker image from `stalwartlabs/mail-server` to `stalwartlabs/stalwart`. - Renamed multiple settings: @@ -28,6 +27,7 @@ This version includes **breaking changes** to the database layout and requires a - `jmap.email.auto-expunge` to `email.auto-expunge`. - `jmap.protocol.changes.max-history` to `changes.max-history`. - `storage.encryption.*` to `email.encryption.*`. +- Deprecated `lookup.default.*` settings in favor of `server.hostname` and `report.domain`. v0.11 and before supported both, v0.12 will only support the new settings. ### Fixed - Allow undiscovered UIDs to be used in IMAP `COPY`/`MOVE` operations (#1201). @@ -36,6 +36,8 @@ This version includes **breaking changes** to the database layout and requires a - Use the newest `X-Spam-Status` Header (#1308). - MySQL Driver error: Transactions couldn't be nested (#1271). - Spawn a delivery thread for `EmailSubmission/set` requests (#1540). +- ACME: Don't restrict challenge types (#1522). +- Autoconfig: return `%EMAILADDRESS%` if no e-mail address is provided (#1537). ## [0.11.8] - 2025-04-30 diff --git a/UPGRADING.md b/UPGRADING.md index 1d26fb43..e286a8d4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -16,7 +16,7 @@ In addition to the database layout changes, multiple settings were renamed: ## Step-by-Step Upgrade Process -- Stop Stalwart: +- Stop Stalwart in **every single node of your cluster**. If you are using the systemd service, you can do this with the following command: ```bash $ sudo systemctl stop stalwart-mail @@ -31,7 +31,7 @@ In addition to the database layout changes, multiple settings were renamed: - Download the `v0.12.0` binary for your platform (which is now called `stalwart` rather than `mail-server`) from the [releases page](https://github.com/stalwartlabs/stalwart/releases/latest/) and replace the binary in `/opt/stalwart-mail/bin`. If you are using the Docker image, pull the latest image. -- Start the service: +- Start the service. In a cluster, you can speed up the migration process by starting all nodes at once. ```bash $ sudo systemctl start stalwart-mail ``` @@ -40,7 +40,7 @@ In addition to the database layout changes, multiple settings were renamed: ## Step-by-Step Upgrade Process (Docker) -- Stop the Stalwart container: +- Stop the Stalwart container in **every single node of your cluster**. If you are using Docker, you can do this with the following command: ```bash $ docker stop stalwart-mail diff --git a/crates/dav/src/calendar/update.rs b/crates/dav/src/calendar/update.rs index 92d66ab6..3ba5b671 100644 --- a/crates/dav/src/calendar/update.rs +++ b/crates/dav/src/calendar/update.rs @@ -283,38 +283,19 @@ fn validate_ical(ical: &ICalendar) -> crate::Result<&str> { // Validate component types let mut types: [u8; 5] = [0; 5]; for comp in &ical.components { - match comp.component_type { - ICalendarComponentType::VEvent => { - types[0] += 1; - if let Some(uid) = comp.uid() { - uids.insert(uid); - } + *(match comp.component_type { + ICalendarComponentType::VEvent => &mut types[0], + ICalendarComponentType::VTodo => &mut types[1], + ICalendarComponentType::VJournal => &mut types[2], + ICalendarComponentType::VFreebusy => &mut types[3], + ICalendarComponentType::VAvailability => &mut types[4], + _ => { + continue; } - ICalendarComponentType::VTodo => { - types[1] += 1; - if let Some(uid) = comp.uid() { - uids.insert(uid); - } - } - ICalendarComponentType::VJournal => { - types[2] += 1; - if let Some(uid) = comp.uid() { - uids.insert(uid); - } - } - ICalendarComponentType::VFreebusy => { - types[3] += 1; - if let Some(uid) = comp.uid() { - uids.insert(uid); - } - } - ICalendarComponentType::VAvailability => { - types[4] += 1; - if let Some(uid) = comp.uid() { - uids.insert(uid); - } - } - _ => {} + }) += 1; + + if let Some(uid) = comp.uid() { + uids.insert(uid); } } diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index b670ff29..f85f886f 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -783,7 +783,7 @@ impl ManageDirectory for Store { } // Delete principal data - self.purge_account(principal_id) + self.danger_destroy_account(principal_id) .await .caused_by(trc::location!())?; diff --git a/crates/store/src/dispatch/store.rs b/crates/store/src/dispatch/store.rs index d369e64e..6f7d949d 100644 --- a/crates/store/src/dispatch/store.rs +++ b/crates/store/src/dispatch/store.rs @@ -380,7 +380,7 @@ impl Store { Ok(()) } - pub async fn purge_account(&self, account_id: u32) -> trc::Result<()> { + pub async fn danger_destroy_account(&self, account_id: u32) -> trc::Result<()> { for subspace in [ SUBSPACE_BITMAP_ID, SUBSPACE_BITMAP_TAG, diff --git a/install.sh b/install.sh index 56db4e35..8c445cd4 100644 --- a/install.sh +++ b/install.sh @@ -7,12 +7,12 @@ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL # -# Stalwart Mail Server install script -- based on the rustup installation script. +# Stalwart install script -- based on the rustup installation script. set -e set -u -readonly BASE_URL="https://github.com/stalwartlabs/mail-server/releases/latest/download" +readonly BASE_URL="https://github.com/stalwartlabs/stalwart/releases/latest/download" main() { downloader --check @@ -32,25 +32,25 @@ main() { # Detect OS local _os="unknown" local _uname="$(uname)" - _account="stalwart-mail" + _account="stalwart" if [ "${_uname}" = "Linux" ]; then _os="linux" elif [ "${_uname}" = "Darwin" ]; then _os="macos" - _account="_stalwart-mail" + _account="_stalwart" fi # Read arguments - local _dir="/opt/stalwart-mail" + local _dir="/opt/stalwart" # Default component setting - local _component="stalwart-mail" + local _component="stalwart" # Loop through the arguments for arg in "$@"; do case "$arg" in --fdb) - _component="stalwart-mail-foundationdb" + _component="stalwart-foundationdb" ;; *) if [ -n "$arg" ]; then @@ -70,15 +70,15 @@ main() { # Download latest binary say "⏳ Downloading ${_component} for ${_arch}..." - local _file="${_dir}/bin/stalwart-mail.tar.gz" + local _file="${_dir}/bin/stalwart.tar.gz" local _url="${BASE_URL}/${_component}-${_arch}.tar.gz" ensure mkdir -p "$_dir" ensure downloader "$_url" "$_file" "$_arch" ensure tar zxvf "$_file" -C "$_dir/bin" - if [ "$_component" = "stalwart-mail-foundationdb" ]; then - ignore mv "$_dir/bin/stalwart-mail-foundationdb" "$_dir/bin/stalwart-mail" + if [ "$_component" = "stalwart-foundationdb" ]; then + ignore mv "$_dir/bin/stalwart-foundationdb" "$_dir/bin/stalwart" fi - ignore chmod +x "$_dir/bin/stalwart-mail" + ignore chmod +x "$_dir/bin/stalwart" ignore rm "$_file" # Create system account @@ -90,30 +90,30 @@ main() { local _uid="$((_last_uid+1))" local _gid="$((_last_gid+1))" - ensure dscl /Local/Default -create Groups/_stalwart-mail - ensure dscl /Local/Default -create Groups/_stalwart-mail Password \* - ensure dscl /Local/Default -create Groups/_stalwart-mail PrimaryGroupID $_gid - ensure dscl /Local/Default -create Groups/_stalwart-mail RealName "Stalwart Mail service" - ensure dscl /Local/Default -create Groups/_stalwart-mail RecordName _stalwart-mail stalwart-mail + ensure dscl /Local/Default -create Groups/_stalwart + ensure dscl /Local/Default -create Groups/_stalwart Password \* + ensure dscl /Local/Default -create Groups/_stalwart PrimaryGroupID $_gid + ensure dscl /Local/Default -create Groups/_stalwart RealName "Stalwart service" + ensure dscl /Local/Default -create Groups/_stalwart RecordName _stalwart stalwart - ensure dscl /Local/Default -create Users/_stalwart-mail - ensure dscl /Local/Default -create Users/_stalwart-mail NFSHomeDirectory /Users/_stalwart-mail - ensure dscl /Local/Default -create Users/_stalwart-mail Password \* - ensure dscl /Local/Default -create Users/_stalwart-mail PrimaryGroupID $_gid - ensure dscl /Local/Default -create Users/_stalwart-mail RealName "Stalwart Mail service" - ensure dscl /Local/Default -create Users/_stalwart-mail RecordName _stalwart-mail stalwart-mail - ensure dscl /Local/Default -create Users/_stalwart-mail UniqueID $_uid - ensure dscl /Local/Default -create Users/_stalwart-mail UserShell /bin/bash + ensure dscl /Local/Default -create Users/_stalwart + ensure dscl /Local/Default -create Users/_stalwart NFSHomeDirectory /Users/_stalwart + ensure dscl /Local/Default -create Users/_stalwart Password \* + ensure dscl /Local/Default -create Users/_stalwart PrimaryGroupID $_gid + ensure dscl /Local/Default -create Users/_stalwart RealName "Stalwart service" + ensure dscl /Local/Default -create Users/_stalwart RecordName _stalwart stalwart + ensure dscl /Local/Default -create Users/_stalwart UniqueID $_uid + ensure dscl /Local/Default -create Users/_stalwart UserShell /bin/bash - ensure dscl /Local/Default -delete /Users/_stalwart-mail AuthenticationAuthority - ensure dscl /Local/Default -delete /Users/_stalwart-mail PasswordPolicyOptions + ensure dscl /Local/Default -delete /Users/_stalwart AuthenticationAuthority + ensure dscl /Local/Default -delete /Users/_stalwart PasswordPolicyOptions else ensure useradd ${_account} -s /usr/sbin/nologin -M -r -U fi fi # Run init - ignore $_dir/bin/stalwart-mail --init "$_dir" + ignore $_dir/bin/stalwart --init "$_dir" # Set permissions say "🔐 Setting permissions..." @@ -139,9 +139,9 @@ main() { # Functions to create service files create_service_linux() { local _dir="$1" - cat < /etc/systemd/system/stalwart-mail.service + cat < /etc/systemd/system/stalwart.service [Unit] -Description=Stalwart Mail Server +Description=Stalwart Conflicts=postfix.service sendmail.service exim4.service ConditionPathExists=__PATH__/etc/config.toml After=network-online.target @@ -153,18 +153,18 @@ KillMode=process KillSignal=SIGINT Restart=on-failure RestartSec=5 -ExecStart=__PATH__/bin/stalwart-mail --config=__PATH__/etc/config.toml -SyslogIdentifier=stalwart-mail -User=stalwart-mail -Group=stalwart-mail +ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.toml +SyslogIdentifier=stalwart +User=stalwart +Group=stalwart AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target EOF systemctl daemon-reload - systemctl enable stalwart-mail.service - systemctl restart stalwart-mail.service + systemctl enable stalwart.service + systemctl restart stalwart.service } create_service_macos() { @@ -178,10 +178,10 @@ create_service_macos() { Label stalwart.mail ServiceDescription - Stalwart Mail Server + Stalwart ProgramArguments - __PATH__/bin/stalwart-mail + __PATH__/bin/stalwart --config=__PATH__/etc/config.toml RunAtLoad diff --git a/install_new.sh b/install_new.sh deleted file mode 100644 index 8c445cd4..00000000 --- a/install_new.sh +++ /dev/null @@ -1,742 +0,0 @@ -#!/usr/bin/env sh -# shellcheck shell=dash - -# -# SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd -# -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL -# - -# Stalwart install script -- based on the rustup installation script. - -set -e -set -u - -readonly BASE_URL="https://github.com/stalwartlabs/stalwart/releases/latest/download" - -main() { - downloader --check - need_cmd uname - need_cmd mktemp - need_cmd chmod - need_cmd mkdir - need_cmd rm - need_cmd rmdir - need_cmd tar - - # Make sure we are running as root - if [ "$(id -u)" -ne 0 ] ; then - err "❌ Install failed: This program needs to run as root." - fi - - # Detect OS - local _os="unknown" - local _uname="$(uname)" - _account="stalwart" - if [ "${_uname}" = "Linux" ]; then - _os="linux" - elif [ "${_uname}" = "Darwin" ]; then - _os="macos" - _account="_stalwart" - fi - - # Read arguments - local _dir="/opt/stalwart" - - # Default component setting - local _component="stalwart" - - # Loop through the arguments - for arg in "$@"; do - case "$arg" in - --fdb) - _component="stalwart-foundationdb" - ;; - *) - if [ -n "$arg" ]; then - _dir=$arg - fi - ;; - esac - done - - # Detect platform architecture - get_architecture || return 1 - local _arch="$RETVAL" - assert_nz "$_arch" "arch" - - # Create directories - ensure mkdir -p "$_dir" "$_dir/bin" "$_dir/etc" "$_dir/logs" - - # Download latest binary - say "⏳ Downloading ${_component} for ${_arch}..." - local _file="${_dir}/bin/stalwart.tar.gz" - local _url="${BASE_URL}/${_component}-${_arch}.tar.gz" - ensure mkdir -p "$_dir" - ensure downloader "$_url" "$_file" "$_arch" - ensure tar zxvf "$_file" -C "$_dir/bin" - if [ "$_component" = "stalwart-foundationdb" ]; then - ignore mv "$_dir/bin/stalwart-foundationdb" "$_dir/bin/stalwart" - fi - ignore chmod +x "$_dir/bin/stalwart" - ignore rm "$_file" - - # Create system account - if ! id -u ${_account} > /dev/null 2>&1; then - say "🖥️ Creating '${_account}' account..." - if [ "${_os}" = "macos" ]; then - local _last_uid="$(dscacheutil -q user | grep uid | awk '{print $2}' | sort -n | tail -n 1)" - local _last_gid="$(dscacheutil -q group | grep gid | awk '{print $2}' | sort -n | tail -n 1)" - local _uid="$((_last_uid+1))" - local _gid="$((_last_gid+1))" - - ensure dscl /Local/Default -create Groups/_stalwart - ensure dscl /Local/Default -create Groups/_stalwart Password \* - ensure dscl /Local/Default -create Groups/_stalwart PrimaryGroupID $_gid - ensure dscl /Local/Default -create Groups/_stalwart RealName "Stalwart service" - ensure dscl /Local/Default -create Groups/_stalwart RecordName _stalwart stalwart - - ensure dscl /Local/Default -create Users/_stalwart - ensure dscl /Local/Default -create Users/_stalwart NFSHomeDirectory /Users/_stalwart - ensure dscl /Local/Default -create Users/_stalwart Password \* - ensure dscl /Local/Default -create Users/_stalwart PrimaryGroupID $_gid - ensure dscl /Local/Default -create Users/_stalwart RealName "Stalwart service" - ensure dscl /Local/Default -create Users/_stalwart RecordName _stalwart stalwart - ensure dscl /Local/Default -create Users/_stalwart UniqueID $_uid - ensure dscl /Local/Default -create Users/_stalwart UserShell /bin/bash - - ensure dscl /Local/Default -delete /Users/_stalwart AuthenticationAuthority - ensure dscl /Local/Default -delete /Users/_stalwart PasswordPolicyOptions - else - ensure useradd ${_account} -s /usr/sbin/nologin -M -r -U - fi - fi - - # Run init - ignore $_dir/bin/stalwart --init "$_dir" - - # Set permissions - say "🔐 Setting permissions..." - ensure chown -R ${_account}:${_account} "$_dir" - ensure chmod -R 755 "$_dir" - ensure chmod 700 "$_dir/etc/config.toml" - - # Create service file - say "🚀 Starting service..." - if [ "${_os}" = "linux" ]; then - create_service_linux "$_dir" - elif [ "${_os}" = "macos" ]; then - create_service_macos "$_dir" - fi - - # Installation complete - local _host=$(hostname -f) - say "🎉 Installation complete! Continue the setup at http://$_host:8080/login" - - return 0 -} - -# Functions to create service files -create_service_linux() { - local _dir="$1" - cat < /etc/systemd/system/stalwart.service -[Unit] -Description=Stalwart -Conflicts=postfix.service sendmail.service exim4.service -ConditionPathExists=__PATH__/etc/config.toml -After=network-online.target - -[Service] -Type=simple -LimitNOFILE=65536 -KillMode=process -KillSignal=SIGINT -Restart=on-failure -RestartSec=5 -ExecStart=__PATH__/bin/stalwart --config=__PATH__/etc/config.toml -SyslogIdentifier=stalwart -User=stalwart -Group=stalwart -AmbientCapabilities=CAP_NET_BIND_SERVICE - -[Install] -WantedBy=multi-user.target -EOF - systemctl daemon-reload - systemctl enable stalwart.service - systemctl restart stalwart.service -} - -create_service_macos() { - local _dir="$1" - cat < /Library/LaunchAgents/stalwart.mail.plist - - - - - Label - stalwart.mail - ServiceDescription - Stalwart - ProgramArguments - - __PATH__/bin/stalwart - --config=__PATH__/etc/config.toml - - RunAtLoad - - KeepAlive - - - -EOF - launchctl load /Library/LaunchAgents/stalwart.mail.plist - launchctl enable system/stalwart.mail - launchctl start system/stalwart.mail -} - - -get_architecture() { - local _ostype _cputype _bitness _arch _clibtype - _ostype="$(uname -s)" - _cputype="$(uname -m)" - _clibtype="gnu" - - if [ "$_ostype" = Linux ]; then - if [ "$(uname -o)" = Android ]; then - _ostype=Android - fi - if ldd --version 2>&1 | grep -q 'musl'; then - _clibtype="musl" - fi - fi - - if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then - # Darwin `uname -m` lies - if sysctl hw.optional.x86_64 | grep -q ': 1'; then - _cputype=x86_64 - fi - fi - - if [ "$_ostype" = SunOS ]; then - # Both Solaris and illumos presently announce as "SunOS" in "uname -s" - # so use "uname -o" to disambiguate. We use the full path to the - # system uname in case the user has coreutils uname first in PATH, - # which has historically sometimes printed the wrong value here. - if [ "$(/usr/bin/uname -o)" = illumos ]; then - _ostype=illumos - fi - - # illumos systems have multi-arch userlands, and "uname -m" reports the - # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 - # systems. Check for the native (widest) instruction set on the - # running kernel: - if [ "$_cputype" = i86pc ]; then - _cputype="$(isainfo -n)" - fi - fi - - case "$_ostype" in - - Android) - _ostype=linux-android - ;; - - Linux) - check_proc - _ostype=unknown-linux-$_clibtype - _bitness=$(get_bitness) - ;; - - FreeBSD) - _ostype=unknown-freebsd - ;; - - NetBSD) - _ostype=unknown-netbsd - ;; - - DragonFly) - _ostype=unknown-dragonfly - ;; - - Darwin) - _ostype=apple-darwin - ;; - - illumos) - _ostype=unknown-illumos - ;; - - MINGW* | MSYS* | CYGWIN* | Windows_NT) - _ostype=pc-windows-gnu - ;; - - *) - err "unrecognized OS type: $_ostype" - ;; - - esac - - case "$_cputype" in - - i386 | i486 | i686 | i786 | x86) - _cputype=i686 - ;; - - xscale | arm) - _cputype=arm - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - fi - ;; - - armv6l) - _cputype=arm - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - - armv7l | armv8l) - _cputype=armv7 - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - - aarch64 | arm64) - _cputype=aarch64 - ;; - - x86_64 | x86-64 | x64 | amd64) - _cputype=x86_64 - ;; - - mips) - _cputype=$(get_endianness mips '' el) - ;; - - mips64) - if [ "$_bitness" -eq 64 ]; then - # only n64 ABI is supported for now - _ostype="${_ostype}abi64" - _cputype=$(get_endianness mips64 '' el) - fi - ;; - - ppc) - _cputype=powerpc - ;; - - ppc64) - _cputype=powerpc64 - ;; - - ppc64le) - _cputype=powerpc64le - ;; - - s390x) - _cputype=s390x - ;; - riscv64) - _cputype=riscv64gc - ;; - *) - err "unknown CPU type: $_cputype" - - esac - - # Detect 64-bit linux with 32-bit userland - if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then - case $_cputype in - x86_64) - if [ -n "${RUSTUP_CPUTYPE:-}" ]; then - _cputype="$RUSTUP_CPUTYPE" - else { - # 32-bit executable for amd64 = x32 - if is_host_amd64_elf; then { - echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2 - echo "and there isn't a native toolchain -- you will have to install" 1>&2 - echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2 - echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2 - echo "set to i686 or x86_64, respectively." 1>&2 - echo 1>&2 - echo "You will be able to add an x32 target after installation by running" 1>&2 - echo " rustup target add x86_64-unknown-linux-gnux32" 1>&2 - exit 1 - }; else - _cputype=i686 - fi - }; fi - ;; - mips64) - _cputype=$(get_endianness mips '' el) - ;; - powerpc64) - _cputype=powerpc - ;; - aarch64) - _cputype=armv7 - if [ "$_ostype" = "linux-android" ]; then - _ostype=linux-androideabi - else - _ostype="${_ostype}eabihf" - fi - ;; - riscv64gc) - err "riscv64 with 32-bit userland unsupported" - ;; - esac - fi - - # Detect armv7 but without the CPU features Rust needs in that build, - # and fall back to arm. - # See https://github.com/rust-lang/rustup.rs/issues/587. - if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then - if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then - # At least one processor does not have NEON. - _cputype=arm - fi - fi - - _arch="${_cputype}-${_ostype}" - - RETVAL="$_arch" -} - -check_proc() { - # Check for /proc by looking for the /proc/self/exe link - # This is only run on Linux - if ! test -L /proc/self/exe ; then - err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." - fi -} - -get_bitness() { - need_cmd head - # Architecture detection without dependencies beyond coreutils. - # ELF files start out "\x7fELF", and the following byte is - # 0x01 for 32-bit and - # 0x02 for 64-bit. - # The printf builtin on some shells like dash only supports octal - # escape sequences, so we use those. - local _current_exe_head - _current_exe_head=$(head -c 5 /proc/self/exe ) - if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then - echo 32 - elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then - echo 64 - else - err "unknown platform bitness" - fi -} - -is_host_amd64_elf() { - need_cmd head - need_cmd tail - # ELF e_machine detection without dependencies beyond coreutils. - # Two-byte field at offset 0x12 indicates the CPU, - # but we're interested in it being 0x3E to indicate amd64, or not that. - local _current_exe_machine - _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) - [ "$_current_exe_machine" = "$(printf '\076')" ] -} - -get_endianness() { - local cputype=$1 - local suffix_eb=$2 - local suffix_el=$3 - - # detect endianness without od/hexdump, like get_bitness() does. - need_cmd head - need_cmd tail - - local _current_exe_endianness - _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" - if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then - echo "${cputype}${suffix_el}" - elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then - echo "${cputype}${suffix_eb}" - else - err "unknown platform endianness" - fi -} - -say() { - printf '%s\n' "$1" -} - -err() { - say "$1" >&2 - exit 1 -} - -need_cmd() { - if ! check_cmd "$1"; then - err "need '$1' (command not found)" - fi -} - -check_cmd() { - command -v "$1" > /dev/null 2>&1 -} - -assert_nz() { - if [ -z "$1" ]; then err "assert_nz $2"; fi -} - -# Run a command that should never fail. If the command fails execution -# will immediately terminate with an error showing the failing -# command. -ensure() { - if ! "$@"; then err "command failed: $*"; fi -} - -# This wraps curl or wget. Try curl first, if not installed, -# use wget instead. -downloader() { - local _dld - local _ciphersuites - local _err - local _status - local _retry - if check_cmd curl; then - _dld=curl - elif check_cmd wget; then - _dld=wget - else - _dld='curl or wget' # to be used in error message of need_cmd - fi - - if [ "$1" = --check ]; then - need_cmd "$_dld" - elif [ "$_dld" = curl ]; then - check_curl_for_retry_support - _retry="$RETVAL" - get_ciphersuites_for_curl - _ciphersuites="$RETVAL" - if [ -n "$_ciphersuites" ]; then - _err=$(curl $_retry --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - else - echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" - if ! check_help_for "$3" curl --proto --tlsv1.2; then - echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" - _err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - else - _err=$(curl $_retry --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1) - _status=$? - fi - fi - if [ -n "$_err" ]; then - if echo "$_err" | grep -q 404; then - err "❌ Binary for platform '$3' not found, this platform may be unsupported." - else - echo "$_err" >&2 - fi - fi - return $_status - elif [ "$_dld" = wget ]; then - if [ "$(wget -V 2>&1|head -2|tail -1|cut -f1 -d" ")" = "BusyBox" ]; then - echo "Warning: using the BusyBox version of wget. Not enforcing strong cipher suites for TLS or TLS v1.2, this is potentially less secure" - _err=$(wget "$1" -O "$2" 2>&1) - _status=$? - else - get_ciphersuites_for_wget - _ciphersuites="$RETVAL" - if [ -n "$_ciphersuites" ]; then - _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1) - _status=$? - else - echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" - if ! check_help_for "$3" wget --https-only --secure-protocol; then - echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" - _err=$(wget "$1" -O "$2" 2>&1) - _status=$? - else - _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1) - _status=$? - fi - fi - fi - if [ -n "$_err" ]; then - if echo "$_err" | grep -q ' 404 Not Found'; then - err "❌ Binary for platform '$3' not found, this platform may be unsupported." - else - echo "$_err" >&2 - fi - fi - return $_status - else - err "Unknown downloader" # should not reach here - fi -} - -# Check if curl supports the --retry flag, then pass it to the curl invocation. -check_curl_for_retry_support() { - local _retry_supported="" - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "curl" "--retry"; then - _retry_supported="--retry 3" - fi - - RETVAL="$_retry_supported" - -} - -check_help_for() { - local _arch - local _cmd - local _arg - _arch="$1" - shift - _cmd="$1" - shift - - local _category - if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then - _category="all" - else - _category="" - fi - - case "$_arch" in - - *darwin*) - if check_cmd sw_vers; then - case $(sw_vers -productVersion) in - 10.*) - # If we're running on macOS, older than 10.13, then we always - # fail to find these options to force fallback - if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then - # Older than 10.13 - echo "Warning: Detected macOS platform older than 10.13" - return 1 - fi - ;; - 11.*) - # We assume Big Sur will be OK for now - ;; - *) - # Unknown product version, warn and continue - echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)" - echo "Warning TLS capabilities detection may fail" - ;; - esac - fi - ;; - - esac - - for _arg in "$@"; do - if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then - return 1 - fi - done - - true # not strictly needed -} - -# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites -# if support by local tools is detected. Detection currently supports these curl backends: -# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. -get_ciphersuites_for_curl() { - if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then - # user specified custom cipher suites, assume they know what they're doing - RETVAL="$RUSTUP_TLS_CIPHERSUITES" - return - fi - - local _openssl_syntax="no" - local _gnutls_syntax="no" - local _backend_supported="yes" - if curl -V | grep -q ' OpenSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' LibreSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' BoringSSL/'; then - _openssl_syntax="yes" - elif curl -V | grep -iq ' GnuTLS/'; then - _gnutls_syntax="yes" - else - _backend_supported="no" - fi - - local _args_supported="no" - if [ "$_backend_supported" = "yes" ]; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then - _args_supported="yes" - fi - fi - - local _cs="" - if [ "$_args_supported" = "yes" ]; then - if [ "$_openssl_syntax" = "yes" ]; then - _cs=$(get_strong_ciphersuites_for "openssl") - elif [ "$_gnutls_syntax" = "yes" ]; then - _cs=$(get_strong_ciphersuites_for "gnutls") - fi - fi - - RETVAL="$_cs" -} - -# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites -# if support by local tools is detected. Detection currently supports these wget backends: -# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. -get_ciphersuites_for_wget() { - if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then - # user specified custom cipher suites, assume they know what they're doing - RETVAL="$RUSTUP_TLS_CIPHERSUITES" - return - fi - - local _cs="" - if wget -V | grep -q '\-DHAVE_LIBSSL'; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then - _cs=$(get_strong_ciphersuites_for "openssl") - fi - elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then - # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. - if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then - _cs=$(get_strong_ciphersuites_for "gnutls") - fi - fi - - RETVAL="$_cs" -} - -# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 -# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad -# DH params often found on servers (see RFC 7919). Sequence matches or is -# similar to Firefox 68 ESR with weak cipher suites disabled via about:config. -# $1 must be openssl or gnutls. -get_strong_ciphersuites_for() { - if [ "$1" = "openssl" ]; then - # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet. - echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" - elif [ "$1" = "gnutls" ]; then - # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't. - # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order. - echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM" - fi -} - -# This is just for indicating that commands' results are being -# intentionally ignored. Usually, because it's being executed -# as part of error handling. -ignore() { - "$@" -} - -main "$@" || exit 1