Skip to content

Commit c70dc3e

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 2f3c439 commit c70dc3e

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
@@ -320,6 +320,24 @@ extern "C" fn cmd_issuer_add_contact(
320320
_cmd: *mut ngx_command_t,
321321
conf: *mut c_void,
322322
) -> *mut c_char {
323+
const MAILTO: &[u8] = b"mailto:";
324+
325+
fn has_scheme(val: &[u8]) -> bool {
326+
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
327+
if !val[0].is_ascii_alphabetic() {
328+
return false;
329+
}
330+
331+
for c in val {
332+
if c.is_ascii_alphanumeric() || matches!(c, b'+' | b'-' | b'.') {
333+
continue;
334+
}
335+
return *c == b':';
336+
}
337+
338+
false
339+
}
340+
323341
let cf = unsafe { cf.as_mut().expect("cf") };
324342
let issuer = unsafe { conf.cast::<Issuer>().as_mut().expect("issuer conf") };
325343

@@ -330,11 +348,25 @@ extern "C" fn cmd_issuer_add_contact(
330348
// NGX_CONF_TAKE1 ensures that args contains 2 elements
331349
let args = cf.args();
332350

333-
if core::str::from_utf8(args[1].as_bytes()).is_err() {
334-
return c"contains invalid UTF-8 sequence".as_ptr().cast_mut();
351+
if args[1].is_empty() || core::str::from_utf8(args[1].as_bytes()).is_err() {
352+
return c"invalid value".as_ptr().cast_mut();
335353
};
336354

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

339371
NGX_CONF_OK
340372
}

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)