mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2024-12-25 09:13:12 +08:00
CI/CD tweaks.
This commit is contained in:
parent
843e61139a
commit
9c6c53e21c
23 changed files with 1707 additions and 224 deletions
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: stalwartlabs
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
306
.github/workflows/build.yml
vendored
Normal file
306
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,306 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Building for ${{ matrix.target }} on ${{ matrix.host_os }}
|
||||
runs-on: ${{ matrix.host_os }}
|
||||
if: '!cancelled()'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
host_os: ubuntu-20.04
|
||||
- target: x86_64-apple-darwin
|
||||
host_os: macos-latest
|
||||
- target: x86_64-pc-windows-msvc
|
||||
host_os: windows-2022
|
||||
#- target: aarch64-unknown-linux-gnu
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: x86_64-unknown-linux-musl
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: aarch64-unknown-linux-musl
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: aarch64-apple-darwin
|
||||
# host_os: macos-latest
|
||||
#- target: aarch64-pc-windows-msvc
|
||||
# host_os: windows-2022
|
||||
#- target: aarch64-pc-windows-msvc
|
||||
# host_os: windows-2022
|
||||
#- target: arm-unknown-linux-musleabihf
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: arm-unknown-linux-gnueabihf
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: armv7-unknown-linux-musleabihf
|
||||
# host_os: ubuntu-20.04
|
||||
#- target: armv7-unknown-linux-gnueabihf
|
||||
# host_os: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: ${{ contains(matrix.host_os, 'ubuntu') }}
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -yq protobuf-compiler wget
|
||||
wget https://github.com/apple/foundationdb/releases/download/7.1.0/foundationdb-clients_7.1.0-1_amd64.deb
|
||||
sudo dpkg -i --force-architecture foundationdb-clients_7.1.0-1_amd64.deb
|
||||
|
||||
- name: Install dependencies (MacOs)
|
||||
if: ${{ contains(matrix.host_os, 'macos') }}
|
||||
run: |
|
||||
brew install protobuf
|
||||
brew install wget
|
||||
wget https://github.com/apple/foundationdb/releases/download/7.1.32/FoundationDB-7.1.32_x86_64.pkg
|
||||
sudo installer -allowUntrusted -dumplog -pkg FoundationDB-7.1.32_x86_64.pkg -target /
|
||||
|
||||
- name: Install dependencies (Windows)
|
||||
if: ${{ contains(matrix.host_os, 'windows') }}
|
||||
uses: arduino/setup-protoc@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
toolchain: stable
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.host_os }}-${{ matrix.target }}-mail
|
||||
|
||||
- name: Building binary (Unix version)
|
||||
if: ${{ !contains(matrix.host_os, 'windows') }}
|
||||
run: |
|
||||
cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --no-default-features --features foundationdb --release
|
||||
cd target/${{ matrix.target }}/release && tar czvf ../../../stalwart-mail-foundationdb-${{ matrix.target }}.tar.gz stalwart-mail && cd -
|
||||
cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cargo build --manifest-path=crates/cli/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cargo build --manifest-path=crates/install/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cd target/${{ matrix.target }}/release
|
||||
tar czvf ../../../stalwart-mail-sqlite-${{ matrix.target }}.tar.gz stalwart-mail
|
||||
tar czvf ../../../stalwart-cli-${{ matrix.target }}.tar.gz stalwart-cli
|
||||
tar czvf ../../../stalwart-install-${{ matrix.target }}.tar.gz stalwart-install
|
||||
cd -
|
||||
|
||||
- name: Building binary (Windows version)
|
||||
if: ${{ contains(matrix.host_os, 'windows') }}
|
||||
run: |
|
||||
cargo build --manifest-path=crates/main/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cargo build --manifest-path=crates/cli/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cargo build --manifest-path=crates/install/Cargo.toml --target=${{ matrix.target }} --release
|
||||
cd target/${{ matrix.target }}/release
|
||||
7z a ../../../stalwart-mail-sqlite-${{ matrix.target }}.zip stalwart-mail.exe
|
||||
7z a ../../../stalwart-cli-${{ matrix.target }}.zip stalwart-cli.exe
|
||||
7z a ../../../stalwart-install-${{ matrix.target }}.zip stalwart-install.exe
|
||||
cd -
|
||||
|
||||
- name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: 'stalwart-*'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
cross_build_tools:
|
||||
runs-on: ubuntu-latest
|
||||
name: Building tools for ${{ matrix.target }} on ${{ matrix.distro }}
|
||||
if: '!cancelled()'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: aarch64
|
||||
distro: ubuntu20.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Build artifact
|
||||
id: build
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# Not required, but speeds up builds
|
||||
githubToken: ${{ github.token }}
|
||||
|
||||
# Create an artifacts directory
|
||||
setup: |
|
||||
mkdir -p "${PWD}/artifacts"
|
||||
|
||||
# Mount the artifacts directory as /artifacts in the container
|
||||
dockerRunArgs: |
|
||||
--volume "${PWD}/artifacts:/artifacts"
|
||||
|
||||
# Pass some environment variables to the container
|
||||
env: |
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
# The shell to run commands with in the container
|
||||
shell: /bin/sh
|
||||
|
||||
install: |
|
||||
apt-get update -yq
|
||||
apt-get install -yq build-essential cmake wget curl
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||||
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
cargo build --manifest-path=crates/cli/Cargo.toml --target=${target} --release
|
||||
cargo build --manifest-path=crates/install/Cargo.toml --target=${target} --release
|
||||
cd target/${target}/release
|
||||
tar czvf /artifacts/stalwart-cli-${target}.tar.gz stalwart-cli
|
||||
tar czvf /artifacts/stalwart-install-${target}.tar.gz stalwart-install
|
||||
cd -
|
||||
|
||||
- name: Move packages
|
||||
run: |
|
||||
mv ${PWD}/artifacts/* .
|
||||
|
||||
- name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: 'stalwart-*'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
cross_build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Building for ${{ matrix.target }} on ${{ matrix.distro }}
|
||||
if: '!cancelled()'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: aarch64
|
||||
distro: ubuntu20.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Build artifact
|
||||
id: build
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# Not required, but speeds up builds
|
||||
githubToken: ${{ github.token }}
|
||||
|
||||
# Create an artifacts directory
|
||||
setup: |
|
||||
mkdir -p "${PWD}/artifacts"
|
||||
|
||||
# Mount the artifacts directory as /artifacts in the container
|
||||
dockerRunArgs: |
|
||||
--volume "${PWD}/artifacts:/artifacts"
|
||||
|
||||
# Pass some environment variables to the container
|
||||
env: |
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
# The shell to run commands with in the container
|
||||
shell: /bin/sh
|
||||
|
||||
install: |
|
||||
apt-get update -yq
|
||||
apt-get install -yq build-essential cmake protobuf-compiler wget curl
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||||
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
cargo build --manifest-path=crates/main/Cargo.toml --target=${target} --release
|
||||
cd target/${target}/release
|
||||
tar czvf /artifacts/stalwart-mail-sqlite-${target}.tar.gz stalwart-mail
|
||||
cd -
|
||||
|
||||
- name: Move packages
|
||||
run: |
|
||||
mv ${PWD}/artifacts/* .
|
||||
|
||||
- name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: 'stalwart-*'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
docker-amd:
|
||||
name: Build Docker AMD64 images
|
||||
if: '!cancelled()'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: stalwartlabs/mail-server:latest
|
||||
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
|
||||
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
|
||||
#cache-from: type=gha
|
||||
#cache-to: type=gha,mode=max
|
||||
|
||||
docker-arm:
|
||||
name: Build Docker ARM64 images
|
||||
if: '!cancelled()'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/arm64
|
||||
tags: stalwartlabs/mail-server:latest
|
||||
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
|
||||
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
|
||||
#cache-from: type=gha
|
||||
#cache-to: type=gha,mode=max
|
110
.github/workflows/test.yml
vendored
Normal file
110
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
style:
|
||||
name: Check Style
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: cargo fmt -- --check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
test:
|
||||
name: Test
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -yq protobuf-compiler
|
||||
wget https://github.com/glauth/glauth/releases/download/v2.2.0/glauth-linux-arm64
|
||||
chmod a+rx glauth-linux-arm64
|
||||
nohup ./glauth-linux-arm64 -c tests/resources/ldap.cfg &
|
||||
wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230629051228.0.0_amd64.deb -O minio.deb
|
||||
sudo dpkg -i minio.deb
|
||||
mkdir ~/minio
|
||||
nohup minio server ~/minio --console-address :9090 &
|
||||
wget https://dl.min.io/client/mc/release/linux-amd64/mc
|
||||
chmod a+rx mc
|
||||
./mc alias set myminio http://localhost:9000 minioadmin minioadmin
|
||||
./mc mb tmp
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: JMAP Protocol Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=crates/jmap-proto/Cargo.toml
|
||||
|
||||
- name: IMAP Protocol Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=crates/imap-proto/Cargo.toml
|
||||
|
||||
- name: Full-text search Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=crates/store/Cargo.toml
|
||||
|
||||
- name: Store Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=tests/Cargo.toml store -- --nocapture
|
||||
|
||||
- name: Directory Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=tests/Cargo.toml directory -- --nocapture
|
||||
|
||||
- name: SMTP Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=tests/Cargo.toml smtp -- --nocapture
|
||||
|
||||
- name: IMAP Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=tests/Cargo.toml imap -- --nocapture
|
||||
|
||||
- name: JMAP Tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path=tests/Cargo.toml jmap -- --nocapture
|
1
CNAME
Normal file
1
CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
get.stalw.art
|
47
Cargo.lock
generated
47
Cargo.lock
generated
|
@ -1296,6 +1296,18 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
|
@ -4385,7 +4397,9 @@ name = "stalwart-install"
|
|||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"clap",
|
||||
"dialoguer",
|
||||
"flate2",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"openssl",
|
||||
|
@ -4394,6 +4408,8 @@ dependencies = [
|
|||
"reqwest",
|
||||
"rpassword",
|
||||
"rusqlite",
|
||||
"tar",
|
||||
"zip-extract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4498,6 +4514,17 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.6.0"
|
||||
|
@ -5534,6 +5561,15 @@ dependencies = [
|
|||
"time 0.3.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.15"
|
||||
|
@ -5572,6 +5608,17 @@ dependencies = [
|
|||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip-extract"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb654964c003959ed64cbd0d7b329bcdcbd9690facd50c8617748d3622543972"
|
||||
dependencies = [
|
||||
"log",
|
||||
"thiserror",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
|
|
52
Dockerfile
Normal file
52
Dockerfile
Normal file
|
@ -0,0 +1,52 @@
|
|||
FROM debian:buster-slim AS chef
|
||||
RUN apt-get update && \
|
||||
export DEBIAN_FRONTEND=noninteractive && \
|
||||
apt-get install -yq \
|
||||
build-essential \
|
||||
cmake \
|
||||
clang \
|
||||
curl \
|
||||
protobuf-compiler
|
||||
ENV RUSTUP_HOME=/opt/rust/rustup \
|
||||
PATH=/home/root/.cargo/bin:/opt/rust/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
RUN curl https://sh.rustup.rs -sSf | \
|
||||
env CARGO_HOME=/opt/rust/cargo \
|
||||
sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path && \
|
||||
env CARGO_HOME=/opt/rust/cargo \
|
||||
rustup component add rustfmt
|
||||
RUN env CARGO_HOME=/opt/rust/cargo cargo install cargo-chef && \
|
||||
rm -rf /opt/rust/cargo/registry/
|
||||
WORKDIR /app
|
||||
|
||||
FROM chef AS planner
|
||||
COPY Cargo.toml .
|
||||
COPY Cargo.lock .
|
||||
COPY crates/ crates/
|
||||
COPY resources/ resources/
|
||||
COPY tests/ tests/
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
COPY Cargo.toml .
|
||||
COPY Cargo.lock .
|
||||
COPY crates/ crates/
|
||||
COPY resources/ resources/
|
||||
COPY tests/ tests/
|
||||
RUN cargo build --manifest-path=crates/main/Cargo.toml --release
|
||||
RUN cargo build --manifest-path=crates/install/Cargo.toml --release
|
||||
|
||||
FROM debian:buster-slim AS runtime
|
||||
|
||||
COPY --from=builder /app/target/release/stalwart-mail /usr/local/bin/stalwart-mail
|
||||
COPY --from=builder /app/target/release/stalwart-install /usr/local/bin/stalwart-install
|
||||
RUN echo "#\!/bin/sh\n\n/usr/local/bin/stalwart-install -c all-in-one -p /opt/stalwart-mail -d" > /usr/local/bin/configure.sh && \
|
||||
chmod +x /usr/local/bin/configure.sh
|
||||
RUN useradd stalwart-mail -s /sbin/nologin -M
|
||||
RUN mkdir -p /opt/stalwart-mail
|
||||
RUN chown stalwart-mail:stalwart-mail /opt/stalwart-mail
|
||||
|
||||
VOLUME [ "/opt/stalwart-mail" ]
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/stalwart-mail", "--config", "/opt/stalwart-mail/etc/config.toml"]
|
|
@ -276,30 +276,3 @@ pub fn decode_challenge_oauth(challenge: &[u8]) -> Result<Credentials<String>, &
|
|||
|
||||
Err("Failed to find 'auth=Bearer' in challenge.")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use mail_send::Credentials;
|
||||
|
||||
#[test]
|
||||
fn decode_challenge_oauth() {
|
||||
assert!(
|
||||
Credentials::OAuthBearer {
|
||||
token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==".to_string()
|
||||
} == super::decode_challenge_oauth(
|
||||
&base64_decode(
|
||||
concat!(
|
||||
"bixhPXVzZXJAZXhhbXBsZS5jb20sAWhv",
|
||||
"c3Q9c2VydmVyLmV4YW1wbGUuY29tAXBvcnQ9MTQzAWF1dGg9QmVhcmVyI",
|
||||
"HZGOWRmdDRxbVRjMk52YjNSbGNrQmhiSFJoZG1semRHRXVZMjl0Q2c9PQ",
|
||||
"EB"
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -303,7 +303,7 @@ impl SessionData {
|
|||
}
|
||||
|
||||
#[allow(clippy::while_let_on_iterator)]
|
||||
fn matches_pattern(patterns: &[String], mailbox_name: &str) -> bool {
|
||||
pub fn matches_pattern(patterns: &[String], mailbox_name: &str) -> bool {
|
||||
if patterns.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
@ -370,79 +370,3 @@ fn matches_pattern(patterns: &[String], mailbox_name: &str) -> bool {
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn matches_pattern() {
|
||||
let mailboxes = [
|
||||
"imaptest",
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
"foobar/test",
|
||||
"foobar/test/test",
|
||||
"foobar/test1/test1",
|
||||
];
|
||||
|
||||
for (pattern, expected_match) in [
|
||||
(
|
||||
"imaptest/%",
|
||||
vec!["imaptest/test", "imaptest/test2", "imaptest/test3"],
|
||||
),
|
||||
("imaptest/%/%", vec!["imaptest/test3/test4"]),
|
||||
(
|
||||
"imaptest/*",
|
||||
vec![
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
],
|
||||
),
|
||||
("imaptest/*test4", vec!["imaptest/test3/test4"]),
|
||||
(
|
||||
"imaptest/*test*",
|
||||
vec![
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
],
|
||||
),
|
||||
("imaptest/%3/%", vec!["imaptest/test3/test4"]),
|
||||
("imaptest/%3/%4", vec!["imaptest/test3/test4"]),
|
||||
("imaptest/%t*4", vec!["imaptest/test3/test4"]),
|
||||
("*st/%3/%4/%5", vec!["imaptest/test3/test4/test5"]),
|
||||
(
|
||||
"*%*%*%",
|
||||
vec![
|
||||
"imaptest",
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
"foobar/test",
|
||||
"foobar/test/test",
|
||||
"foobar/test1/test1",
|
||||
],
|
||||
),
|
||||
("foobar*test", vec!["foobar/test", "foobar/test/test"]),
|
||||
] {
|
||||
let patterns = vec![pattern.to_string()];
|
||||
let mut matched_mailboxes = Vec::new();
|
||||
for mailbox in mailboxes {
|
||||
if super::matches_pattern(&patterns, mailbox) {
|
||||
matched_mailboxes.push(mailbox);
|
||||
}
|
||||
}
|
||||
assert_eq!(matched_mailboxes, expected_match, "for pattern {}", pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ readme = "README.md"
|
|||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots"]}
|
||||
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots", "blocking"] }
|
||||
rusqlite = { version = "0.29.0", features = ["bundled"] }
|
||||
rpassword = "7.0"
|
||||
indicatif = "0.17.0"
|
||||
|
@ -20,6 +20,12 @@ openssl = { version = "0.10.55", features = ["vendored"] }
|
|||
base64 = "0.21.2"
|
||||
pwhash = "1.0.0"
|
||||
rand = "0.8.5"
|
||||
clap = { version = "4.1.6", features = ["derive"] }
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
libc = "0.2.147"
|
||||
flate2 = "1.0.26"
|
||||
tar = "0.4.38"
|
||||
|
||||
[target.'cfg(target_env = "msvc")'.dependencies]
|
||||
zip-extract = "0.1.2"
|
||||
|
|
6
crates/install/build.rs
Normal file
6
crates/install/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
println!(
|
||||
"cargo:rustc-env=TARGET={}",
|
||||
std::env::var("TARGET").unwrap()
|
||||
);
|
||||
}
|
|
@ -1,15 +1,20 @@
|
|||
use std::{
|
||||
fs,
|
||||
io::Cursor,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select};
|
||||
use flate2::bufread::GzDecoder;
|
||||
use openssl::rsa::Rsa;
|
||||
use pwhash::sha512_crypt;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
use tar::Archive;
|
||||
|
||||
const CFG_COMMON: &str = include_str!("../../../resources/config/common.toml");
|
||||
const CFG_DIRECTORY: &str = include_str!("../../../resources/config/directory.toml");
|
||||
|
@ -17,7 +22,25 @@ const CFG_JMAP: &str = include_str!("../../../resources/config/jmap.toml");
|
|||
const CFG_IMAP: &str = include_str!("../../../resources/config/imap.toml");
|
||||
const CFG_SMTP: &str = include_str!("../../../resources/config/smtp.toml");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg(target_os = "linux")]
|
||||
const SERVICE: &str = include_str!("../../../resources/systemd/stalwart-mail.service");
|
||||
#[cfg(target_os = "macos")]
|
||||
const SERVICE: &str = include_str!("../../../resources/systemd/stalwart.mail.plist");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const ACCOUNT_NAME: &str = "stalwart-mail";
|
||||
#[cfg(target_os = "macos")]
|
||||
const ACCOUNT_NAME: &str = "_stalwart-mail";
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
const PKG_EXTENSION: &str = "tar.gz";
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
const PKG_EXTENSION: &str = "zip";
|
||||
|
||||
static TARGET: &str = env!("TARGET");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
enum Component {
|
||||
AllInOne,
|
||||
Jmap,
|
||||
|
@ -36,23 +59,23 @@ enum Blob {
|
|||
Local,
|
||||
MinIO,
|
||||
S3,
|
||||
GCS,
|
||||
Gcs,
|
||||
Azure,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Directory {
|
||||
SQL,
|
||||
LDAP,
|
||||
Sql,
|
||||
Ldap,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum SmtpDirectory {
|
||||
SQL,
|
||||
LDAP,
|
||||
LMTP,
|
||||
IMAP,
|
||||
Sql,
|
||||
Ldap,
|
||||
Lmtp,
|
||||
Imap,
|
||||
}
|
||||
|
||||
const DIRECTORIES: [[&str; 2]; 6] = [
|
||||
|
@ -64,28 +87,63 @@ const DIRECTORIES: [[&str; 2]; 6] = [
|
|||
["reports", ""],
|
||||
];
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
#[clap(name = "stalwart-cli")]
|
||||
pub struct Arguments {
|
||||
#[clap(long, short = 'p')]
|
||||
path: Option<PathBuf>,
|
||||
#[clap(long, short = 'c')]
|
||||
component: Option<Component>,
|
||||
#[clap(long, short = 'd')]
|
||||
docker: bool,
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let c = "fix";
|
||||
/*#[cfg(not(target_env = "msvc"))]
|
||||
let args = Arguments::parse();
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
unsafe {
|
||||
if libc::getuid() != 0 {
|
||||
eprintln!("This program must be run as root.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
println!("\nWelcome to the Stalwart mail server installer\n");
|
||||
println!("\nWelcome to the Stalwart Mail Server installer\n");
|
||||
|
||||
let component = select::<Component>(
|
||||
"Which components would you like to install?",
|
||||
&[
|
||||
"All-in-one mail server (JMAP + IMAP + SMTP)",
|
||||
"JMAP server",
|
||||
"IMAP server",
|
||||
"SMTP server",
|
||||
],
|
||||
Component::AllInOne,
|
||||
)?;
|
||||
// Obtain component to install
|
||||
let (component, skip_download) = if let Some(component) = args.component {
|
||||
(component, true)
|
||||
} else {
|
||||
(
|
||||
select::<Component>(
|
||||
"Which components would you like to install?",
|
||||
&[
|
||||
"All-in-one mail server (JMAP + IMAP + SMTP)",
|
||||
"JMAP server",
|
||||
"IMAP server",
|
||||
"SMTP server",
|
||||
],
|
||||
Component::AllInOne,
|
||||
)?,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
// Obtain base path
|
||||
let base_path = if let Some(base_path) = args.path {
|
||||
base_path
|
||||
} else {
|
||||
PathBuf::from(input(
|
||||
"Installation directory",
|
||||
component.default_base_path(),
|
||||
dir_create_if_missing,
|
||||
)?)
|
||||
};
|
||||
create_directories(&base_path)?;
|
||||
|
||||
// Build configuration file
|
||||
let mut cfg_file = match component {
|
||||
Component::AllInOne | Component::Imap => {
|
||||
[CFG_COMMON, CFG_DIRECTORY, CFG_JMAP, CFG_IMAP, CFG_SMTP].join("\n")
|
||||
|
@ -93,15 +151,46 @@ fn main() -> std::io::Result<()> {
|
|||
Component::Jmap => [CFG_COMMON, CFG_DIRECTORY, CFG_JMAP, CFG_SMTP].join("\n"),
|
||||
Component::Smtp => [CFG_COMMON, CFG_DIRECTORY, CFG_SMTP].join("\n"),
|
||||
};
|
||||
let mut download_url = None;
|
||||
|
||||
// Obtain database engine
|
||||
let directory = if component != Component::Smtp {
|
||||
let backend = select::<Backend>(
|
||||
"Which database engine would you like to use?",
|
||||
&[
|
||||
"SQLite (single node, replicated with Litestream)",
|
||||
"FoundationDB (distributed and fault-tolerant)",
|
||||
],
|
||||
Backend::SQLite,
|
||||
)?;
|
||||
if !skip_download {
|
||||
let backend = select::<Backend>(
|
||||
"Which database engine would you like to use?",
|
||||
&[
|
||||
"SQLite (single node, replicated with Litestream)",
|
||||
"FoundationDB (distributed and fault-tolerant)",
|
||||
],
|
||||
Backend::SQLite,
|
||||
)?;
|
||||
|
||||
download_url = format!(
|
||||
concat!(
|
||||
"https://github.com/stalwartlabs/{}",
|
||||
"/releases/latest/download/stalwart-{}-{}-{}.{}"
|
||||
),
|
||||
match component {
|
||||
Component::AllInOne => "mail-server",
|
||||
Component::Jmap => "jmap-server",
|
||||
Component::Imap => "imap-server",
|
||||
Component::Smtp => unreachable!(),
|
||||
},
|
||||
match component {
|
||||
Component::AllInOne => "mail",
|
||||
Component::Jmap => "jmap",
|
||||
Component::Imap => "imap",
|
||||
Component::Smtp => unreachable!(),
|
||||
},
|
||||
match backend {
|
||||
Backend::SQLite => "sqlite",
|
||||
Backend::FoundationDB => "foundationdb",
|
||||
},
|
||||
TARGET,
|
||||
PKG_EXTENSION
|
||||
)
|
||||
.into();
|
||||
}
|
||||
let blob = select::<Blob>(
|
||||
"Where would you like to store e-mails and blobs?",
|
||||
&[
|
||||
|
@ -135,15 +224,15 @@ fn main() -> std::io::Result<()> {
|
|||
.replace(
|
||||
"__DIRECTORY__",
|
||||
match directory {
|
||||
Directory::SQL | Directory::None => "sql",
|
||||
Directory::LDAP => "ldap",
|
||||
Directory::Sql | Directory::None => "sql",
|
||||
Directory::Ldap => "ldap",
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
"__SMTP_DIRECTORY__",
|
||||
match directory {
|
||||
Directory::SQL | Directory::None => "sql",
|
||||
Directory::LDAP => "ldap",
|
||||
Directory::Sql | Directory::None => "sql",
|
||||
Directory::Ldap => "ldap",
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
|
@ -165,40 +254,100 @@ fn main() -> std::io::Result<()> {
|
|||
"LMTP server",
|
||||
"IMAP server",
|
||||
],
|
||||
SmtpDirectory::LMTP,
|
||||
SmtpDirectory::Lmtp,
|
||||
)?;
|
||||
cfg_file = cfg_file
|
||||
.replace("__NEXT_HOP__", "lmtp")
|
||||
.replace(
|
||||
"__SMTP_DIRECTORY__",
|
||||
match smtp_directory {
|
||||
SmtpDirectory::SQL => "sql",
|
||||
SmtpDirectory::LDAP => "ldap",
|
||||
SmtpDirectory::LMTP => "lmtp",
|
||||
SmtpDirectory::IMAP => "imap",
|
||||
SmtpDirectory::Sql => "sql",
|
||||
SmtpDirectory::Ldap => "ldap",
|
||||
SmtpDirectory::Lmtp => "lmtp",
|
||||
SmtpDirectory::Imap => "imap",
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
"__DIRECTORY__",
|
||||
match smtp_directory {
|
||||
SmtpDirectory::SQL | SmtpDirectory::LMTP | SmtpDirectory::IMAP => "sql",
|
||||
SmtpDirectory::LDAP => "ldap",
|
||||
SmtpDirectory::Sql | SmtpDirectory::Lmtp | SmtpDirectory::Imap => "sql",
|
||||
SmtpDirectory::Ldap => "ldap",
|
||||
},
|
||||
)
|
||||
.replace("__NEXT_HOP__", "lmtp");
|
||||
|
||||
if !skip_download {
|
||||
download_url = format!(
|
||||
concat!(
|
||||
"https://github.com/stalwartlabs/smtp-server",
|
||||
"/releases/latest/download/stalwart-smtp-{}.{}"
|
||||
),
|
||||
TARGET, PKG_EXTENSION
|
||||
)
|
||||
.into();
|
||||
}
|
||||
match smtp_directory {
|
||||
SmtpDirectory::SQL => Directory::SQL,
|
||||
SmtpDirectory::LDAP => Directory::LDAP,
|
||||
SmtpDirectory::LMTP | SmtpDirectory::IMAP => Directory::None,
|
||||
SmtpDirectory::Sql => Directory::Sql,
|
||||
SmtpDirectory::Ldap => Directory::Ldap,
|
||||
SmtpDirectory::Lmtp | SmtpDirectory::Imap => Directory::None,
|
||||
}
|
||||
};
|
||||
let base_path = PathBuf::from(input(
|
||||
"Installation directory",
|
||||
component.default_base_path(),
|
||||
dir_create_if_missing,
|
||||
)?);
|
||||
create_directories(&base_path)?;
|
||||
|
||||
// Download binary
|
||||
if let Some(download_url) = download_url {
|
||||
eprintln!("📦 Downloading components...");
|
||||
for url in [
|
||||
download_url,
|
||||
format!(
|
||||
concat!(
|
||||
"https://github.com/stalwartlabs/mail-server",
|
||||
"/releases/latest/download/stalwart-cli-{}.{}"
|
||||
),
|
||||
TARGET, PKG_EXTENSION
|
||||
),
|
||||
] {
|
||||
match reqwest::blocking::get(&url).and_then(|r| {
|
||||
if r.status().is_success() {
|
||||
r.bytes().map(Ok)
|
||||
} else {
|
||||
Ok(Err(r))
|
||||
}
|
||||
}) {
|
||||
Ok(Ok(bytes)) => {
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
if let Err(err) = Archive::new(GzDecoder::new(Cursor::new(bytes)))
|
||||
.unpack(base_path.join("bin"))
|
||||
{
|
||||
eprintln!("❌ Failed to unpack {}: {}", url, err);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
if let Err(err) =
|
||||
zip_extract::extract(Cursor::new(bytes), &base_path.join("bin"), true)
|
||||
{
|
||||
eprintln!("❌ Failed to unpack {}: {}", url, err);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(Err(response)) => {
|
||||
eprintln!(
|
||||
"❌ Failed to download {}, make sure your platform is supported: {}",
|
||||
url,
|
||||
response.status()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
eprintln!("❌ Failed to download {}: {}", url, err);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain domain name
|
||||
let domain = input(
|
||||
"What is your main domain name? (you can add others later)",
|
||||
"yourdomain.org",
|
||||
|
@ -214,18 +363,37 @@ fn main() -> std::io::Result<()> {
|
|||
.trim()
|
||||
.to_lowercase();
|
||||
|
||||
let cert_path = input(
|
||||
&format!("Where is the TLS certificate for '{hostname}' located?"),
|
||||
&format!("/etc/letsencrypt/live/{hostname}/fullchain.pem"),
|
||||
file_exists,
|
||||
)?;
|
||||
let pk_path = input(
|
||||
&format!("Where is the TLS private key for '{hostname}' located?"),
|
||||
&format!("/etc/letsencrypt/live/{hostname}/privkey.pem"),
|
||||
file_exists,
|
||||
)?;
|
||||
// Obtain TLS certificate path
|
||||
let (cert_path, pk_path) = if !args.docker {
|
||||
(
|
||||
input(
|
||||
&format!("Where is the TLS certificate for '{hostname}' located?"),
|
||||
&format!("/etc/letsencrypt/live/{hostname}/fullchain.pem"),
|
||||
file_exists,
|
||||
)?,
|
||||
input(
|
||||
&format!("Where is the TLS private key for '{hostname}' located?"),
|
||||
&format!("/etc/letsencrypt/live/{hostname}/privkey.pem"),
|
||||
file_exists,
|
||||
)?,
|
||||
)
|
||||
} else {
|
||||
// Create directories
|
||||
fs::create_dir_all(base_path.join("etc").join("certs").join(&hostname))?;
|
||||
(
|
||||
format!(
|
||||
"{}/etc/certs/{}/fullchain.pem",
|
||||
base_path.display(),
|
||||
hostname
|
||||
),
|
||||
format!("{}/etc/certs/{}/privkey.pem", base_path.display(), hostname),
|
||||
)
|
||||
};
|
||||
|
||||
// Generate DKIM key and instructions
|
||||
let dkim_instructions = generate_dkim(&base_path, &domain, &hostname)?;
|
||||
|
||||
// Create authentication SQLite database
|
||||
let admin_password = if matches!(directory, Directory::None) {
|
||||
create_auth_db(&base_path, &domain)?.into()
|
||||
} else {
|
||||
|
@ -245,6 +413,13 @@ fn main() -> std::io::Result<()> {
|
|||
));
|
||||
fs::rename(&cfg_path, backup_path)?;
|
||||
}
|
||||
if args.docker {
|
||||
cfg_file = cfg_file
|
||||
.replace("127.0.0.1:8686", "0.0.0.0:8686")
|
||||
.replace("[server.run-as]", "#[server.run-as]")
|
||||
.replace("user = \"stalwart-mail\"", "#user = \"stalwart-mail\"")
|
||||
.replace("group = \"stalwart-mail\"", "#group = \"stalwart-mail\"");
|
||||
}
|
||||
fs::write(
|
||||
cfg_path,
|
||||
cfg_file
|
||||
|
@ -255,6 +430,107 @@ fn main() -> std::io::Result<()> {
|
|||
.replace("__PK_PATH__", &pk_path),
|
||||
)?;
|
||||
|
||||
// Write service file
|
||||
if !args.docker {
|
||||
// Change permissions
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
{
|
||||
let mut cmd = Command::new("chown");
|
||||
cmd.arg("-R")
|
||||
.arg(format!("{}:{}", ACCOUNT_NAME, ACCOUNT_NAME))
|
||||
.arg(&base_path);
|
||||
if let Err(err) = cmd.status() {
|
||||
eprintln!("Warning: Failed to set permissions: {}", err);
|
||||
}
|
||||
let mut cmd = Command::new("chmod");
|
||||
cmd.arg("-R")
|
||||
.arg("770")
|
||||
.arg(&format!("{}/etc", base_path.display()))
|
||||
.arg(&format!("{}/data", base_path.display()))
|
||||
.arg(&format!("{}/queue", base_path.display()))
|
||||
.arg(&format!("{}/reports", base_path.display()))
|
||||
.arg(&format!("{}/logs", base_path.display()));
|
||||
if let Err(err) = cmd.status() {
|
||||
eprintln!("Warning: Failed to set permissions: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let service_file = format!(
|
||||
"/etc/systemd/system/stalwart-{}.service",
|
||||
component.binary_name()
|
||||
);
|
||||
let service_name = format!("stalwart-{}", component.binary_name());
|
||||
match fs::write(
|
||||
&service_file,
|
||||
SERVICE
|
||||
.replace("__PATH__", base_path.to_str().unwrap())
|
||||
.replace("__NAME__", component.binary_name())
|
||||
.replace("__TITLE__", component.name()),
|
||||
) {
|
||||
Ok(_) => {
|
||||
if let Err(err) = Command::new("/bin/systemctl")
|
||||
.arg("enable")
|
||||
.arg(service_file)
|
||||
.status()
|
||||
.and_then(|_| {
|
||||
Command::new("/bin/systemctl")
|
||||
.arg("restart")
|
||||
.arg(&service_name)
|
||||
.status()
|
||||
})
|
||||
{
|
||||
eprintln!("Warning: Failed to start service: {}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Warning: Failed to write service file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let service_file = format!(
|
||||
"/Library/LaunchDaemons/stalwart.{}.plist",
|
||||
component.binary_name()
|
||||
);
|
||||
let service_name = format!("system/stalwart.{}", component.binary_name());
|
||||
match fs::write(
|
||||
&service_file,
|
||||
SERVICE
|
||||
.replace("__PATH__", base_path.to_str().unwrap())
|
||||
.replace("__NAME__", component.binary_name())
|
||||
.replace("__TITLE__", component.name()),
|
||||
) {
|
||||
Ok(_) => {
|
||||
if let Err(err) = Command::new("launchctl")
|
||||
.arg("load")
|
||||
.arg(service_file)
|
||||
.status()
|
||||
.and_then(|_| {
|
||||
Command::new("launchctl")
|
||||
.arg("enable")
|
||||
.arg(&service_name)
|
||||
.status()
|
||||
})
|
||||
.and_then(|_| {
|
||||
Command::new("launchctl")
|
||||
.arg("start")
|
||||
.arg(&service_name)
|
||||
.status()
|
||||
})
|
||||
{
|
||||
eprintln!("Warning: Failed to start service: {}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Warning: Failed to write service file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("\n🎉 Installation completed!\n\n✅ {dkim_instructions}\n");
|
||||
|
||||
if let Some(admin_password) = admin_password {
|
||||
|
@ -419,11 +695,12 @@ fn generate_dkim(path: &Path, domain: &str, hostname: &str) -> std::io::Result<S
|
|||
Ok(instructions)
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
/*#[cfg(not(target_env = "msvc"))]
|
||||
unsafe fn get_uid_gid() -> (libc::uid_t, libc::gid_t) {
|
||||
use std::process::Command;
|
||||
let pw = libc::getpwnam("stalwart-mail".as_ptr() as *const i8);
|
||||
let gr = libc::getgrnam("stalwart-mail".as_ptr() as *const i8);
|
||||
use std::{ffi::CString, process::Command};
|
||||
let c_str = CString::new("stalwart-mail").unwrap();
|
||||
let pw = libc::getpwnam(c_str.as_ptr());
|
||||
let gr = libc::getgrnam(c_str.as_ptr());
|
||||
|
||||
if pw.is_null() || gr.is_null() {
|
||||
let mut cmd = Command::new("useradd");
|
||||
|
@ -436,13 +713,13 @@ unsafe fn get_uid_gid() -> (libc::uid_t, libc::gid_t) {
|
|||
eprintln!("Failed to create stalwart system account: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let pw = libc::getpwnam("stalwart-mail".as_ptr() as *const i8);
|
||||
let gr = libc::getgrnam("stalwart-mail".as_ptr() as *const i8);
|
||||
let pw = libc::getpwnam(c_str.as_ptr());
|
||||
let gr = libc::getgrnam(c_str.as_ptr());
|
||||
(pw.as_ref().unwrap().pw_uid, gr.as_ref().unwrap().gr_gid)
|
||||
} else {
|
||||
((*pw).pw_uid, ((*gr).gr_gid))
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
trait SelectItem {
|
||||
fn from_index(index: usize) -> Self;
|
||||
|
@ -490,8 +767,8 @@ impl SelectItem for Backend {
|
|||
impl SelectItem for Directory {
|
||||
fn from_index(index: usize) -> Self {
|
||||
match index {
|
||||
0 => Self::SQL,
|
||||
1 => Self::LDAP,
|
||||
0 => Self::Sql,
|
||||
1 => Self::Ldap,
|
||||
2 => Self::None,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -499,8 +776,8 @@ impl SelectItem for Directory {
|
|||
|
||||
fn to_index(&self) -> usize {
|
||||
match self {
|
||||
Self::SQL => 0,
|
||||
Self::LDAP => 1,
|
||||
Self::Sql => 0,
|
||||
Self::Ldap => 1,
|
||||
Self::None => 2,
|
||||
}
|
||||
}
|
||||
|
@ -509,20 +786,20 @@ impl SelectItem for Directory {
|
|||
impl SelectItem for SmtpDirectory {
|
||||
fn from_index(index: usize) -> Self {
|
||||
match index {
|
||||
0 => Self::SQL,
|
||||
1 => Self::LDAP,
|
||||
2 => Self::LMTP,
|
||||
3 => Self::IMAP,
|
||||
0 => Self::Sql,
|
||||
1 => Self::Ldap,
|
||||
2 => Self::Lmtp,
|
||||
3 => Self::Imap,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_index(&self) -> usize {
|
||||
match self {
|
||||
SmtpDirectory::SQL => 0,
|
||||
SmtpDirectory::LDAP => 1,
|
||||
SmtpDirectory::LMTP => 2,
|
||||
SmtpDirectory::IMAP => 3,
|
||||
SmtpDirectory::Sql => 0,
|
||||
SmtpDirectory::Ldap => 1,
|
||||
SmtpDirectory::Lmtp => 2,
|
||||
SmtpDirectory::Imap => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -533,7 +810,7 @@ impl SelectItem for Blob {
|
|||
0 => Blob::Local,
|
||||
1 => Blob::MinIO,
|
||||
2 => Blob::S3,
|
||||
3 => Blob::GCS,
|
||||
3 => Blob::Gcs,
|
||||
4 => Blob::Azure,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -544,7 +821,7 @@ impl SelectItem for Blob {
|
|||
Blob::Local => 0,
|
||||
Blob::MinIO => 1,
|
||||
Blob::S3 => 2,
|
||||
Blob::GCS => 3,
|
||||
Blob::Gcs => 3,
|
||||
Blob::Azure => 4,
|
||||
}
|
||||
}
|
||||
|
@ -559,4 +836,22 @@ impl Component {
|
|||
Self::Smtp => "/opt/stalwart-smtp",
|
||||
}
|
||||
}
|
||||
|
||||
fn binary_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AllInOne => "mail",
|
||||
Self::Jmap => "jmap",
|
||||
Self::Imap => "imap",
|
||||
Self::Smtp => "smtp",
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AllInOne => "Mail",
|
||||
Self::Jmap => "JMAP",
|
||||
Self::Imap => "IMAP",
|
||||
Self::Smtp => "SMTP",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -206,29 +206,3 @@ pub fn generate_iv(nonce: &[u8], counter: usize) -> [u8; ECE_NONCE_LENGTH] {
|
|||
iv[offset..].copy_from_slice(&(mask ^ (counter as u64)).to_be_bytes());
|
||||
iv
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ece_roundtrip() {
|
||||
for len in [1, 2, 5, 16, 256, 1024, 2048, 4096, 1024 * 1024] {
|
||||
let (keypair, auth_secret) = ece::generate_keypair_and_auth_secret().unwrap();
|
||||
|
||||
let bytes: Vec<u8> = (0..len).map(|_| store::rand::random::<u8>()).collect();
|
||||
|
||||
let encrypted_bytes =
|
||||
ece_encrypt(&keypair.pub_as_raw().unwrap(), &auth_secret, &bytes).unwrap();
|
||||
|
||||
let decrypted_bytes = ece::decrypt(
|
||||
&keypair.raw_components().unwrap(),
|
||||
&auth_secret,
|
||||
&encrypted_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(bytes, decrypted_bytes, "len: {}", len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ num_cpus = { version = "1.15.0", optional = true }
|
|||
blake3 = "1.3.3"
|
||||
tracing = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23", features = ["full"] }
|
||||
|
||||
[features]
|
||||
rocks = ["rocksdb", "rayon", "is_sync", "backend"]
|
||||
sqlite = ["rusqlite", "rayon", "r2d2", "num_cpus", "is_sync", "backend"]
|
||||
|
|
641
install.sh
Normal file
641
install.sh
Normal file
|
@ -0,0 +1,641 @@
|
|||
#!/usr/bin/env sh
|
||||
# shellcheck shell=dash
|
||||
|
||||
# Stalwart SMTP install script -- based on the rustup installation script.
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
readonly BASE_URL="https://github.com/stalwartlabs/mail-server/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-mail"
|
||||
if [ "${_uname}" = "Linux" ]; then
|
||||
_os="linux"
|
||||
elif [ "${_uname}" = "Darwin" ]; then
|
||||
_os="macos"
|
||||
_account="_stalwart-mail"
|
||||
fi
|
||||
|
||||
# Start configuration mode
|
||||
if [ "$#" -eq 1 ] && [ "$1" = "--init" ] ; then
|
||||
init
|
||||
configure
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Detect platform architecture
|
||||
get_architecture || return 1
|
||||
local _arch="$RETVAL"
|
||||
assert_nz "$_arch" "arch"
|
||||
|
||||
# Download latest binary
|
||||
say "⏳ Downloading installer for ${_arch}..."
|
||||
local _dir
|
||||
_dir="$(ensure mktemp -d)"
|
||||
local _file="${_dir}/stalwart-install.tar.gz"
|
||||
local _url="${BASE_URL}/stalwart-install-${_arch}.tar.gz"
|
||||
ensure mkdir -p "$_dir"
|
||||
ensure downloader "$_url" "$_file" "$_arch"
|
||||
|
||||
# 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-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 SMTP service"
|
||||
ensure dscl /Local/Default -create Groups/_stalwart-mail RecordName _stalwart-mail stalwart-mail
|
||||
|
||||
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 SMTP 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 -delete /Users/_stalwart-mail AuthenticationAuthority
|
||||
ensure dscl /Local/Default -delete /Users/_stalwart-mail PasswordPolicyOptions
|
||||
else
|
||||
ensure useradd ${_account} -s /sbin/nologin -M
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy binary
|
||||
say "⬇️ Running installer..."
|
||||
ensure tar zxvf "$_file" -C "$_dir"
|
||||
ignore $_dir/stalwart-install
|
||||
ignore rm "$_file"
|
||||
ignore rm "$_dir/stalwart-install"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
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 'stalwart-mail: %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
|
|
@ -48,7 +48,7 @@ shared-map = {shard = 32, capacity = 10}
|
|||
[global.tracing]
|
||||
method = "log"
|
||||
path = "__PATH__/logs"
|
||||
prefix = "smtp.log"
|
||||
prefix = "stalwart.log"
|
||||
rotate = "daily"
|
||||
level = "info"
|
||||
|
||||
|
|
|
@ -72,8 +72,7 @@ wait = "5s"
|
|||
relay = [ { if = "authenticated-as", ne = "", then = true },
|
||||
{ else = false } ]
|
||||
max-recipients = 25
|
||||
directory = [ { if = "authenticated-as", ne = "", then = "__SMTP_DIRECTORY__" },
|
||||
{ else = false } ]
|
||||
directory = "__SMTP_DIRECTORY__"
|
||||
|
||||
[session.rcpt.cache]
|
||||
entries = 1000
|
||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
[Unit]
|
||||
Description=Stalwart SMTP
|
||||
Description=Stalwart __TITLE__ Server
|
||||
Conflicts=postfix.service sendmail.service exim4.service
|
||||
ConditionPathExists=/usr/local/stalwart-smtp/etc/config.toml
|
||||
ConditionPathExists=__PATH__/etc/config.toml
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
|
@ -11,11 +11,11 @@ KillMode=process
|
|||
KillSignal=SIGINT
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
ExecStart=/usr/local/stalwart-smtp/bin/stalwart-smtp --config=/usr/local/stalwart-smtp/etc/config.toml
|
||||
ExecStart=__PATH__/bin/stalwart-__NAME__ --config=__PATH__/etc/config.toml
|
||||
PermissionsStartOnly=true
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=stalwart-smtp
|
||||
SyslogIdentifier=stalwart-__NAME__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -4,13 +4,13 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>stalwart.smtp</string>
|
||||
<string>stalwart.__NAME__</string>
|
||||
<key>ServiceDescription</key>
|
||||
<string>Stalwart SMTP Server</string>
|
||||
<string>Stalwart __TITLE__ Server</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/stalwart-smtp/bin/stalwart-smtp</string>
|
||||
<string>--config=/usr/local/stalwart-smtp/etc/config.toml</string>
|
||||
<string>__PATH__/bin/stalwart-__NAME__</string>
|
||||
<string>--config=__PATH__/etc/config.toml</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
|
@ -21,7 +21,10 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
use imap::op::authenticate::decode_challenge_oauth;
|
||||
use imap_proto::ResponseType;
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use mail_send::Credentials;
|
||||
|
||||
use super::{AssertResult, ImapConnection, Type};
|
||||
|
||||
|
@ -50,3 +53,24 @@ pub async fn test(imap: &mut ImapConnection, _imap_check: &mut ImapConnection) {
|
|||
imap.send_untagged("AGJvYXR5AG1jYm9hdGZhY2U=").await;
|
||||
imap.assert_read(Type::Tagged, ResponseType::No).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_challenge() {
|
||||
assert!(
|
||||
Credentials::OAuthBearer {
|
||||
token: "vF9dft4qmTc2Nvb3RlckBhbHRhdmlzdGEuY29tCg==".to_string()
|
||||
} == decode_challenge_oauth(
|
||||
&base64_decode(
|
||||
concat!(
|
||||
"bixhPXVzZXJAZXhhbXBsZS5jb20sAWhv",
|
||||
"c3Q9c2VydmVyLmV4YW1wbGUuY29tAXBvcnQ9MTQzAWF1dGg9QmVhcmVyI",
|
||||
"HZGOWRmdDRxbVRjMk52YjNSbGNrQmhiSFJoZG1semRHRXVZMjl0Q2c9PQ",
|
||||
"EB"
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* for more details.
|
||||
*/
|
||||
|
||||
use imap::op::list::matches_pattern;
|
||||
use imap_proto::ResponseType;
|
||||
|
||||
use super::{AssertResult, ImapConnection, Type};
|
||||
|
@ -338,3 +339,75 @@ pub async fn test(mut imap: &mut ImapConnection, mut imap_check: &mut ImapConnec
|
|||
imap.send("RENAME \"Recycle Bin\" \"Deleted Items\"").await;
|
||||
imap.assert_read(Type::Tagged, ResponseType::Ok).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mailbox_matches_pattern() {
|
||||
let mailboxes = [
|
||||
"imaptest",
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
"foobar/test",
|
||||
"foobar/test/test",
|
||||
"foobar/test1/test1",
|
||||
];
|
||||
|
||||
for (pattern, expected_match) in [
|
||||
(
|
||||
"imaptest/%",
|
||||
vec!["imaptest/test", "imaptest/test2", "imaptest/test3"],
|
||||
),
|
||||
("imaptest/%/%", vec!["imaptest/test3/test4"]),
|
||||
(
|
||||
"imaptest/*",
|
||||
vec![
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
],
|
||||
),
|
||||
("imaptest/*test4", vec!["imaptest/test3/test4"]),
|
||||
(
|
||||
"imaptest/*test*",
|
||||
vec![
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
],
|
||||
),
|
||||
("imaptest/%3/%", vec!["imaptest/test3/test4"]),
|
||||
("imaptest/%3/%4", vec!["imaptest/test3/test4"]),
|
||||
("imaptest/%t*4", vec!["imaptest/test3/test4"]),
|
||||
("*st/%3/%4/%5", vec!["imaptest/test3/test4/test5"]),
|
||||
(
|
||||
"*%*%*%",
|
||||
vec![
|
||||
"imaptest",
|
||||
"imaptest/test",
|
||||
"imaptest/test2",
|
||||
"imaptest/test3",
|
||||
"imaptest/test3/test4",
|
||||
"imaptest/test3/test4/test5",
|
||||
"foobar/test",
|
||||
"foobar/test/test",
|
||||
"foobar/test1/test1",
|
||||
],
|
||||
),
|
||||
("foobar*test", vec!["foobar/test", "foobar/test/test"]),
|
||||
] {
|
||||
let patterns = vec![pattern.to_string()];
|
||||
let mut matched_mailboxes = Vec::new();
|
||||
for mailbox in mailboxes {
|
||||
if matches_pattern(&patterns, mailbox) {
|
||||
matched_mailboxes.push(mailbox);
|
||||
}
|
||||
}
|
||||
assert_eq!(matched_mailboxes, expected_match, "for pattern {}", pattern);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,13 +247,27 @@ pub async fn jmap_tests() {
|
|||
email_submission::test(params.server.clone(), &mut params.client).await;
|
||||
websocket::test(params.server.clone(), &mut params.client).await;
|
||||
quota::test(params.server.clone(), &mut params.client).await;
|
||||
stress_test::test(params.server.clone(), params.client).await;
|
||||
|
||||
if delete {
|
||||
params.temp_dir.delete();
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
pub async fn jmap_stress_tests() {
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::WARN)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let params = init_jmap_tests(true).await;
|
||||
stress_test::test(params.server.clone(), params.client).await;
|
||||
params.temp_dir.delete();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct JMAPTest {
|
||||
server: Arc<JMAP>,
|
||||
|
|
|
@ -39,6 +39,7 @@ use jmap::{
|
|||
HtmlResponse, StateChangeResponse,
|
||||
},
|
||||
auth::AccessToken,
|
||||
push::ece::ece_encrypt,
|
||||
JMAP,
|
||||
};
|
||||
use jmap_client::{client::Client, mailbox::Role, push_subscription::Keys};
|
||||
|
@ -375,3 +376,24 @@ async fn assert_state(event_rx: &mut mpsc::Receiver<PushMessage>, id: &Id, state
|
|||
state.iter().collect::<AHashSet<&TypeState>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ece_roundtrip() {
|
||||
for len in [1, 2, 5, 16, 256, 1024, 2048, 4096, 1024 * 1024] {
|
||||
let (keypair, auth_secret) = ece::generate_keypair_and_auth_secret().unwrap();
|
||||
|
||||
let bytes: Vec<u8> = (0..len).map(|_| store::rand::random::<u8>()).collect();
|
||||
|
||||
let encrypted_bytes =
|
||||
ece_encrypt(&keypair.pub_as_raw().unwrap(), &auth_secret, &bytes).unwrap();
|
||||
|
||||
let decrypted_bytes = ece::decrypt(
|
||||
&keypair.raw_components().unwrap(),
|
||||
&auth_secret,
|
||||
&encrypted_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(bytes, decrypted_bytes, "len: {}", len);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue