Skip to content

Commit 3cc3b72

Browse files
committed
v0.7.2
1 parent 929d844 commit 3cc3b72

File tree

13 files changed

+59
-21
lines changed

13 files changed

+59
-21
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
6+
## [0.7.2] - 2024-04-17
7+
8+
To upgrade replace the `stalwart-mail` binary and then upgrade to the latest web-admin version.
9+
10+
## Added
11+
- Support for DNS-01 and HTTP-01 ACME challenges (#226)
12+
- Configurable external resources (#355)
13+
14+
### Changed
15+
16+
### Fixed
17+
- Startup failure when Elasticsearch is down/starting up (#334)
18+
- URL decode path elements in REST API.
19+
520
## [0.7.1] - 2024-04-12
621

722
To upgrade replace the `stalwart-mail` binary.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Key features:
9191
- Self-service portal for password reset and encryption-at-rest key management.
9292
- **Secure and robust**:
9393
- Encryption at rest with **S/MIME** or **OpenPGP**.
94-
- Automatic TLS certificate provisioning with [ACME](https://datatracker.ietf.org/doc/html/rfc8555).
94+
- Automatic TLS certificate provisioning with [ACME](https://datatracker.ietf.org/doc/html/rfc8555) using `TLS-ALPN-01`, `DNS-01` or `HTTP-01` challenges.
9595
- OAuth 2.0 [authorization code](https://www.rfc-editor.org/rfc/rfc8628) and [device authorization](https://www.rfc-editor.org/rfc/rfc8628) flows.
9696
- Automated blocking of hosts that cause multiple authentication errors (aka **fail2ban**).
9797
- Access Control Lists (ACLs).

crates/common/src/config/server/tls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,10 @@ fn build_dns_updater(config: &mut Config, acme_id: &str) -> Option<DnsUpdater> {
203203
match config.value_require(("acme", acme_id, "provider"))? {
204204
"rfc2136-tsig" => {
205205
let algorithm: TsigAlgorithm = config
206-
.value_require(("acme", acme_id, "algorithm"))?
206+
.value_require(("acme", acme_id, "tsig-algorithm"))?
207207
.parse()
208208
.map_err(|_| {
209-
config.new_parse_error(("acme", acme_id, "algorithm"), "Invalid algorithm")
209+
config.new_parse_error(("acme", acme_id, "tsig-algorithm"), "Invalid algorithm")
210210
})
211211
.ok()?;
212212
let key = STANDARD

crates/jmap/src/api/management/dkim.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ use crate::{
4646
JMAP,
4747
};
4848

49+
use super::decode_path_element;
50+
4951
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
5052
pub enum Algorithm {
5153
Rsa,
@@ -76,7 +78,7 @@ impl JMAP {
7678

7779
async fn handle_get_public_key(&self, path: Vec<&str>) -> HttpResponse {
7880
let signature_id = match path.get(1) {
79-
Some(signature_id) => *signature_id,
81+
Some(signature_id) => decode_path_element(signature_id),
8082
None => {
8183
return RequestError::not_found().into_http_response();
8284
}

crates/jmap/src/api/management/domain.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use crate::{
3939
JMAP,
4040
};
4141

42+
use super::decode_path_element;
43+
4244
#[derive(Debug, Serialize, Deserialize)]
4345
struct DnsRecord {
4446
#[serde(rename = "type")]
@@ -82,7 +84,8 @@ impl JMAP {
8284
}
8385
(Some(domain), &Method::GET) => {
8486
// Obtain DNS records
85-
match self.build_dns_records(domain).await {
87+
let domain = decode_path_element(domain);
88+
match self.build_dns_records(domain.as_ref()).await {
8689
Ok(records) => JsonResponse::new(json!({
8790
"data": records,
8891
}))
@@ -92,7 +95,8 @@ impl JMAP {
9295
}
9396
(Some(domain), &Method::POST) => {
9497
// Create domain
95-
match self.core.storage.data.create_domain(domain).await {
98+
let domain = decode_path_element(domain);
99+
match self.core.storage.data.create_domain(domain.as_ref()).await {
96100
Ok(_) => {
97101
// Set default domain name if missing
98102
if matches!(
@@ -103,7 +107,7 @@ impl JMAP {
103107
.core
104108
.storage
105109
.config
106-
.set([("lookup.default.domain", *domain)])
110+
.set([("lookup.default.domain", domain.as_ref())])
107111
.await
108112
{
109113
tracing::error!("Failed to set default domain name: {}", err);
@@ -120,7 +124,8 @@ impl JMAP {
120124
}
121125
(Some(domain), &Method::DELETE) => {
122126
// Delete domain
123-
match self.core.storage.data.delete_domain(domain).await {
127+
let domain = decode_path_element(domain);
128+
match self.core.storage.data.delete_domain(domain.as_ref()).await {
124129
Ok(_) => JsonResponse::new(json!({
125130
"data": (),
126131
}))

crates/jmap/src/api/management/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,12 @@ impl From<String> for ManagementApiError {
128128
}
129129
}
130130
}
131+
132+
pub fn decode_path_element(item: &str) -> Cow<'_, str> {
133+
// Bit hackish but avoids an extra dependency
134+
form_urlencoded::parse(item.as_bytes())
135+
.into_iter()
136+
.next()
137+
.map(|(k, _)| k)
138+
.unwrap_or_else(|| item.into())
139+
}

crates/jmap/src/api/management/principal.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::{
4242
JMAP,
4343
};
4444

45-
use super::ManagementApiError;
45+
use super::{decode_path_element, ManagementApiError};
4646

4747
#[derive(Debug, serde::Serialize, serde::Deserialize)]
4848
pub struct PrincipalResponse {
@@ -151,7 +151,8 @@ impl JMAP {
151151
}
152152
(Some(name), method) => {
153153
// Fetch, update or delete principal
154-
let account_id = match self.core.storage.data.get_account_id(name).await {
154+
let name = decode_path_element(name);
155+
let account_id = match self.core.storage.data.get_account_id(name.as_ref()).await {
155156
Ok(Some(account_id)) => account_id,
156157
Ok(None) => {
157158
return RequestError::blank(

crates/jmap/src/api/management/queue.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ use crate::{
4545
JMAP,
4646
};
4747

48+
use super::decode_path_element;
49+
4850
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
4951
pub struct Message {
5052
pub id: QueueId,
@@ -122,7 +124,7 @@ impl JMAP {
122124

123125
match (
124126
path.get(1).copied().unwrap_or_default(),
125-
path.get(2).copied(),
127+
path.get(2).copied().map(decode_path_element),
126128
req.method(),
127129
) {
128130
("messages", None, &Method::GET) => {
@@ -439,7 +441,7 @@ impl JMAP {
439441
}
440442
("reports", Some(report_id), &Method::GET) => {
441443
let mut result = None;
442-
if let Some(report_id) = parse_queued_report_id(report_id) {
444+
if let Some(report_id) = parse_queued_report_id(report_id.as_ref()) {
443445
match report_id {
444446
QueueClass::DmarcReportHeader(event) => {
445447
let mut rua = Vec::new();
@@ -475,7 +477,7 @@ impl JMAP {
475477
}
476478
}
477479
("reports", Some(report_id), &Method::DELETE) => {
478-
if let Some(report_id) = parse_queued_report_id(report_id) {
480+
if let Some(report_id) = parse_queued_report_id(report_id.as_ref()) {
479481
match report_id {
480482
QueueClass::DmarcReportHeader(event) => {
481483
self.smtp.delete_dmarc_report(event).await;

crates/jmap/src/api/management/report.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ use crate::{
4040
JMAP,
4141
};
4242

43+
use super::decode_path_element;
44+
4345
enum ReportType {
4446
Dmarc,
4547
Tls,
@@ -50,7 +52,7 @@ impl JMAP {
5052
pub async fn handle_manage_reports(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
5153
match (
5254
path.get(1).copied().unwrap_or_default(),
53-
path.get(2).copied(),
55+
path.get(2).copied().map(decode_path_element),
5456
req.method(),
5557
) {
5658
(class @ ("dmarc" | "tls" | "arf"), None, &Method::GET) => {
@@ -159,7 +161,7 @@ impl JMAP {
159161
}
160162
}
161163
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::GET) => {
162-
if let Some(report_id) = parse_incoming_report_id(class, report_id) {
164+
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
163165
match &report_id {
164166
ReportClass::Tls { .. } => match self
165167
.core
@@ -215,7 +217,7 @@ impl JMAP {
215217
}
216218
}
217219
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::DELETE) => {
218-
if let Some(report_id) = parse_incoming_report_id(class, report_id) {
220+
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
219221
let mut batch = BatchBuilder::new();
220222
batch.clear(ValueClass::Report(report_id));
221223
let result = self.core.storage.data.write(batch.build()).await.is_ok();

crates/jmap/src/api/management/settings.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::{
3232
JMAP,
3333
};
3434

35-
use super::ManagementApiError;
35+
use super::{decode_path_element, ManagementApiError};
3636

3737
#[derive(Debug, serde::Serialize, serde::Deserialize)]
3838
#[serde(tag = "type")]
@@ -269,7 +269,9 @@ impl JMAP {
269269
}
270270
}
271271
(Some(prefix), &Method::DELETE) if !prefix.is_empty() => {
272-
match self.core.storage.config.clear(prefix).await {
272+
let prefix = decode_path_element(prefix);
273+
274+
match self.core.storage.config.clear(prefix.as_ref()).await {
273275
Ok(_) => JsonResponse::new(json!({
274276
"data": (),
275277
}))

crates/utils/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ chrono = "0.4"
2121
rand = "0.8.5"
2222
webpki-roots = { version = "0.26"}
2323
ring = { version = "0.17" }
24-
base64 = "0.21"
24+
base64 = "0.22"
2525
serde_json = "1.0"
2626
rcgen = "0.13"
2727
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]}

tests/resources/acme/docker-compose-pebble.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version: '3'
88
services:
99
pebble:
1010
image: letsencrypt/pebble:latest
11-
command: pebble -config /test/config/pebble-config.json -strict -dnsserver 8.8.8.8:53 #-dnsserver 10.30.50.3:8053
11+
command: pebble -config /test/config/pebble-config.json -strict -dnsserver 10.30.50.3:8053 #-dnsserver 8.8.8.8:53
1212
ports:
1313
- 14000:14000 # HTTPS ACME API
1414
- 15000:15000 # HTTPS Management API

0 commit comments

Comments
 (0)