Skip to content

Commit 27111ff

Browse files
committed
ACME: assume "mailto:" scheme by default for contact URLs.
The only contact URL scheme required in RFC8555 is mailto:. While other schemes are allowed and can be accepted by an ACME server, most of the clients ignore that. The "contact" directive parser is updated to prepend mailto: to any URLs without a scheme. This can be resolved unambiguously, as ':' is not allowed in the email address unquoted, and quotes are forbidden in the URL scheme.
1 parent 0f4e7f2 commit 27111ff

File tree

3 files changed

+39
-6
lines changed

3 files changed

+39
-6
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ resolver 127.0.0.1:53;
5454
5555
acme_issuer example {
5656
uri https://acme.example.com/directory;
57-
contact mailto:[email protected];
57+
5858
state_path /var/lib/nginx/acme-example;
5959
accept_terms_of_service;
6060
}
@@ -134,7 +134,8 @@ restart unless [](#state_path) is configured.
134134
**Context:** acme_issuer
135135

136136
An array of URLs that the ACME server can use to contact the client for issues
137-
related to this account.
137+
related to this account. The `mailto:` scheme will be assumed unless specified
138+
explicitly.
138139

139140
Can be specified multiple times.
140141

src/conf.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,24 @@ extern "C" fn cmd_issuer_add_contact(
317317
_cmd: *mut ngx_command_t,
318318
conf: *mut c_void,
319319
) -> *mut c_char {
320+
const MAILTO: &[u8] = b"mailto:";
321+
322+
fn has_scheme(val: &[u8]) -> bool {
323+
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
324+
if !val[0].is_ascii_alphabetic() {
325+
return false;
326+
}
327+
328+
for c in val {
329+
if c.is_ascii_alphanumeric() || matches!(c, b'+' | b'-' | b'.') {
330+
continue;
331+
}
332+
return *c == b':';
333+
}
334+
335+
false
336+
}
337+
320338
let cf = unsafe { cf.as_mut().expect("cf") };
321339
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };
322340

@@ -327,11 +345,25 @@ extern "C" fn cmd_issuer_add_contact(
327345
// NGX_CONF_TAKE1 ensures that args contains 2 elements
328346
let args = cf.args();
329347

330-
if core::str::from_utf8(args[1].as_bytes()).is_err() {
331-
return c"contains invalid UTF-8 sequence".as_ptr().cast_mut();
348+
if args[1].is_empty() || core::str::from_utf8(args[1].as_bytes()).is_err() {
349+
return c"invalid value".as_ptr().cast_mut();
332350
};
333351

334-
issuer.contacts.push(args[1]);
352+
if has_scheme(args[1].as_ref()) {
353+
issuer.contacts.push(args[1]);
354+
} else {
355+
let mut value = ngx_str_t::empty();
356+
value.len = MAILTO.len() + args[1].len;
357+
value.data = cf.pool().alloc_unaligned(value.len).cast();
358+
if value.data.is_null() {
359+
return NGX_CONF_ERROR;
360+
}
361+
362+
value.as_bytes_mut()[..MAILTO.len()].copy_from_slice(MAILTO);
363+
value.as_bytes_mut()[MAILTO.len()..].copy_from_slice(args[1].as_ref());
364+
365+
issuer.contacts.push(value);
366+
}
335367

336368
NGX_CONF_OK
337369
}

t/acme_conf_issuer.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ acme_shared_zone zone=ngx_acme_shared:1M;
6767
acme_issuer example {
6868
uri https://localhost:%%PORT_9000%%/dir;
6969
account_key ecdsa:256;
70-
contact mailto:[email protected];
70+
7171
resolver 127.0.0.1:%%PORT_8980_UDP%%;
7272
resolver_timeout 5s;
7373
ssl_verify off;

0 commit comments

Comments
 (0)