Skip to content

Commit e422f56

Browse files
committed
smtp_server: add smtp_server_get_dynamic_parameters event
This helps to deploy IP-based virtual service.
1 parent 71c8584 commit e422f56

9 files changed

Lines changed: 161 additions & 6 deletions

File tree

crates/kumod/src/smtp_server.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ impl ConcreteEsmtpListenerParams {
276276
) {
277277
if let Some(hostname) = base.hostname {
278278
self.hostname = hostname;
279+
meta.set_meta("hostname", self.hostname.to_string());
279280
}
280281
if let Some(relay_hosts) = base.relay_hosts {
281282
self.relay_hosts = relay_hosts;
@@ -386,7 +387,7 @@ impl Default for ConcreteEsmtpListenerParams {
386387
}
387388
}
388389

389-
#[derive(Deserialize, Clone, Debug, PartialEq)]
390+
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
390391
#[serde(deny_unknown_fields)]
391392
pub struct GenericEsmtpListenerParams {
392393
#[serde(default)]
@@ -443,6 +444,12 @@ pub struct GenericEsmtpListenerParams {
443444
line_length_hard_limit: Option<usize>,
444445
}
445446

447+
impl mlua::FromLua for GenericEsmtpListenerParams {
448+
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> Result<Self, mlua::Error> {
449+
config::from_lua_value(lua, value)
450+
}
451+
}
452+
446453
#[derive(Deserialize, Clone, Debug)]
447454
#[serde(deny_unknown_fields)]
448455
pub struct EsmtpListenerParams {
@@ -674,10 +681,10 @@ impl SmtpServerSession {
674681
meta.set_meta("received_from", peer_address.to_string());
675682

676683
let mut concrete_params = ConcreteEsmtpListenerParams::default();
677-
concrete_params.apply_generic(params.base, &my_address, &peer_address, &mut meta);
678-
679684
meta.set_meta("hostname", concrete_params.hostname.to_string());
680685

686+
concrete_params.apply_generic(params.base, &my_address, &peer_address, &mut meta);
687+
681688
let service = format!("esmtp_listener:{my_address}");
682689

683690
let mut server = SmtpServerSession {
@@ -1258,6 +1265,27 @@ impl SmtpServerSession {
12581265
return Ok(());
12591266
}
12601267

1268+
match self
1269+
.call_callback::<GenericEsmtpListenerParams, _, _>(
1270+
"smtp_server_get_dynamic_parameters",
1271+
(self.my_address.to_string(), self.meta.clone()),
1272+
)
1273+
.await?
1274+
{
1275+
Ok(generic) => {
1276+
self.params.apply_generic(
1277+
generic,
1278+
&self.my_address,
1279+
&self.peer_address,
1280+
&mut self.meta,
1281+
);
1282+
}
1283+
Err(rej) => {
1284+
self.write_response(rej.code, rej.message, None).await?;
1285+
return Ok(());
1286+
}
1287+
}
1288+
12611289
if let Err(rej) = self
12621290
.call_callback::<(), _, _>("smtp_server_connection_accepted", self.meta.clone())
12631291
.await?

crates/mailparsing/src/conformance.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use serde::Deserialize;
1+
use serde::{Deserialize, Serialize};
22

3-
#[derive(Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
3+
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
44
pub enum ConformanceDisposition {
55
#[default]
66
Deny,

docs/changelog/main.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
* SMTP Server: new
1919
[smtp_server_connection_accepted](../reference/events/smtp_server_connection_accepted.md)
2020
event allows custom processing prior to returning the banner to the client.
21+
* SMTP Server: new
22+
[smtp_server_get_dynamic_parameters](../reference/events/smtp_server_get_dynamic_parameters.md)
23+
event allows dynamically amending listener configuration to support IP-based
24+
virtual service.
2125

2226
## Fixes
2327

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# `kumo.on('smtp_server_get_dynamic_parameters', function(listener, conn_meta))`
2+
3+
{{since('dev')}}
4+
5+
!!! note
6+
This option is primarily intended to be used together with
7+
a wildcard `listen` value of `0.0.0.0` for an IPv4 listener
8+
or `::` for an IPv6 listener where you desire to dynamically
9+
configure IP based virtual MTA service.
10+
11+
Called by the ESMTP server when a new server session has accepted
12+
a connection from a client, and offers a chance to update the
13+
configuration for the listener dynamically. This event triggers
14+
before [smtp_server_connection_accepted](smtp_server_connection_accepted.md).
15+
16+
The parameters are:
17+
18+
* `listener` - the stringified version of the listener address, such as `0.0.0.0:25`
19+
* `conn_meta` - the [Connection Metadata](../connectionmeta.md) object
20+
21+
The return value must be a table holding ESMTP listener parameter *overrides*
22+
that you wish to apply to the existing listener parameters for this connection.
23+
The fields that you specify in the return value will override the fields that
24+
were already configured. Almost every field described under
25+
[kumo.start_esmtp_listener](../kumo/start_esmtp_listener/index.md) can be used;
26+
those that cannot will indicate it in their individual documentation pages.
27+
28+
The following example is equivalent to the
29+
[via](../kumo/start_esmtp_listener/via.md) example, except that rather than the
30+
`via` parameters being statically configured during the `init` event, they are
31+
computed for every new connection:
32+
33+
```lua
34+
kumo.on('init', function()
35+
kumo.start_esmtp_listener {
36+
listen = '0.0.0.0:25',
37+
}
38+
end)
39+
40+
kumo.on('smtp_server_get_dynamic_parameters', function(listener, conn_meta)
41+
return {
42+
via = {
43+
-- When clients connect to this server via its 10.0.0.1 IP
44+
-- address, we will use the hostname and TLS parameters
45+
-- defined in this block
46+
['10.0.0.1'] = {
47+
hostname = 'mx.example-customer.com',
48+
tls_certificate = '/path/to/customer1.cert',
49+
tls_private_key = '/path/to/customer1.key',
50+
},
51+
-- When clients connect to this server via its 10.0.0.2 IP
52+
-- address, we will use the hostname and TLS parameters
53+
-- defined in this block
54+
['10.0.0.2'] = {
55+
hostname = 'mx.other-customer.com',
56+
tls_certificate = '/path/to/customer2.cert',
57+
tls_private_key = '/path/to/customer2.key',
58+
},
59+
},
60+
}
61+
end)
62+
```
63+
64+
This late binding of the configuration can be used to aid in dynamically
65+
updating the set of listeners on a wildcard port.
66+
67+
For example, you could put the listener overrides into a TOML or JSON
68+
file that has the same shape as the listener parameters:
69+
70+
```toml
71+
[via.'10.0.0.1']
72+
hostname = 'mx.example-customer.com'
73+
tls_certificate = '/path/to/customer1.cert'
74+
tls_private_key = '/path/to/customer1.key'
75+
76+
[via.'10.0.0.2']
77+
hostname = 'mx.other-customer.com'
78+
tls_certificate = '/path/to/customer2.cert'
79+
tls_private_key = '/path/to/customer2.key'
80+
```
81+
82+
Then change the policy code to load it:
83+
84+
```lua
85+
kumo.on('init', function()
86+
kumo.start_esmtp_listener {
87+
listen = '0.0.0.0:25',
88+
}
89+
end)
90+
91+
kumo.on('smtp_server_get_dynamic_parameters', function(listener, conn_meta)
92+
return kumo.serde.toml_load '/opt/kumometa/etc/policy/listener_params.toml'
93+
end)
94+
```
95+
96+
Depending upon the size of the data you are loading, and especially if you
97+
choose to load data from an external service, you should consider using
98+
[kumo.memoize](../kumo/memoize.md) to cache the data.

docs/reference/kumo/start_esmtp_listener/_index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,13 @@ kumo.on('init', function()
2020
end)
2121
```
2222

23+
!!! note
24+
You can also use the
25+
[smtp_server_get_dynamic_parameters](../../events/smtp_server_get_dynamic_parameters.md)
26+
event to dynamically adjust listener parameters. You cannot bind
27+
new ports or IPs that way, but if you are using the "any" address
28+
such as `0.0.0.0` or `::`, you can dynamically refine the parameters
29+
for IP-based virtual service.
30+
2331
`PARAMS` is a lua table that can accept the keys listed below:
2432

docs/reference/kumo/start_esmtp_listener/listen.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,9 @@ kumo.start_esmtp_listener {
1111
}
1212
```
1313

14-
14+
!!! note
15+
This option cannot be used in dynamic listener contexts such as within
16+
[via](via.md), [peer](peer.md) or within the parameters returned from
17+
[smtp_server_get_dynamic_parameters](../../events/smtp_server_get_dynamic_parameters.md).
18+
It can only be used directly at the top level within the
19+
`kumo.start_esmtp_listener` call.

docs/reference/kumo/start_esmtp_listener/max_connections.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ In earlier releases, there was no kumod-controlled upper bound on the
1818
number of connections, and as many as the kernel allowed would be
1919
permitted.
2020

21+
!!! note
22+
This option cannot be used in dynamic listener contexts such as within
23+
[via](via.md), [peer](peer.md) or within the parameters returned from
24+
[smtp_server_get_dynamic_parameters](../../events/smtp_server_get_dynamic_parameters.md).
25+
It can only be used directly at the top level within the
26+
`kumo.start_esmtp_listener` call.
2127

docs/reference/kumo/start_esmtp_listener/peer.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ kumo.start_esmtp_listener {
3131
}
3232
```
3333

34+
See also:
35+
36+
* [smtp_server_get_dynamic_parameters](../../events/smtp_server_get_dynamic_parameters.md)

docs/reference/kumo/start_esmtp_listener/via.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ kumo.start_esmtp_listener {
3939
}
4040
```
4141

42+
See also:
43+
44+
* [smtp_server_get_dynamic_parameters](../../events/smtp_server_get_dynamic_parameters.md)

0 commit comments

Comments
 (0)