mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2024-09-20 07:16:18 +08:00
Trusted/untrusted Sieve runtimes
This commit is contained in:
parent
73ccf7be65
commit
b5fe327e83
|
@ -85,10 +85,10 @@ impl crate::Config {
|
|||
.property("jmap.email.parse.max-items")?
|
||||
.unwrap_or(10),
|
||||
sieve_max_script_name: settings
|
||||
.property("sieve.jmap.limits.name-length")?
|
||||
.property("sieve.untrusted.limits.name-length")?
|
||||
.unwrap_or(512),
|
||||
sieve_max_scripts: settings
|
||||
.property("sieve.jmap.limits.max-scripts")?
|
||||
.property("sieve.untrusted.limits.max-scripts")?
|
||||
.unwrap_or(256),
|
||||
capabilities: BaseCapabilities::default(),
|
||||
session_cache_ttl: settings
|
||||
|
|
|
@ -379,7 +379,7 @@ impl SieveCapabilities {
|
|||
pub fn new(config: &crate::Config, settings: &utils::config::Config) -> Self {
|
||||
let mut notification_methods = Vec::new();
|
||||
|
||||
for (_, uri) in settings.values("sieve.jmap.notification-uris") {
|
||||
for (_, uri) in settings.values("sieve.untrusted.notification-uris") {
|
||||
notification_methods.push(uri.to_string());
|
||||
}
|
||||
if notification_methods.is_empty() {
|
||||
|
@ -389,7 +389,7 @@ impl SieveCapabilities {
|
|||
let mut capabilities: AHashSet<sieve::compiler::grammar::Capability> =
|
||||
AHashSet::from_iter(sieve::compiler::grammar::Capability::all().iter().cloned());
|
||||
|
||||
for (_, capability) in settings.values("sieve.jmap.disabled-capabilities") {
|
||||
for (_, capability) in settings.values("sieve.untrusted.disabled-capabilities") {
|
||||
capabilities.remove(&sieve::compiler::grammar::Capability::parse(capability));
|
||||
}
|
||||
|
||||
|
@ -402,12 +402,12 @@ impl SieveCapabilities {
|
|||
SieveCapabilities {
|
||||
max_script_name: config.sieve_max_script_name,
|
||||
max_script_size: settings
|
||||
.property("sieve.jmap.max-script-size")
|
||||
.property("sieve.untrusted.max-script-size")
|
||||
.failed("Invalid configuration file")
|
||||
.unwrap_or(1024 * 1024),
|
||||
max_scripts: config.sieve_max_scripts,
|
||||
max_redirects: settings
|
||||
.property("sieve.jmap.max-redirects")
|
||||
.property("sieve.untrusted.max-redirects")
|
||||
.failed("Invalid configuration file")
|
||||
.unwrap_or(1),
|
||||
extensions,
|
||||
|
|
|
@ -228,98 +228,110 @@ impl JMAP {
|
|||
sieve_compiler: Compiler::new()
|
||||
.with_max_script_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.script-size")?
|
||||
.property("sieve.untrusted.limits.script-size")?
|
||||
.unwrap_or(1024 * 1024),
|
||||
)
|
||||
.with_max_string_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.string-length")?
|
||||
.property("sieve.untrusted.limits.string-length")?
|
||||
.unwrap_or(4096),
|
||||
)
|
||||
.with_max_variable_name_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.variable-name-length")?
|
||||
.property("sieve.untrusted.limits.variable-name-length")?
|
||||
.unwrap_or(32),
|
||||
)
|
||||
.with_max_nested_blocks(
|
||||
config
|
||||
.property("sieve.jmap.limits.nested-blocks")?
|
||||
.property("sieve.untrusted.limits.nested-blocks")?
|
||||
.unwrap_or(15),
|
||||
)
|
||||
.with_max_nested_tests(
|
||||
config
|
||||
.property("sieve.jmap.limits.nested-tests")?
|
||||
.property("sieve.untrusted.limits.nested-tests")?
|
||||
.unwrap_or(15),
|
||||
)
|
||||
.with_max_nested_foreverypart(
|
||||
config
|
||||
.property("sieve.jmap.limits.nested-foreverypart")?
|
||||
.property("sieve.untrusted.limits.nested-foreverypart")?
|
||||
.unwrap_or(3),
|
||||
)
|
||||
.with_max_match_variables(
|
||||
config
|
||||
.property("sieve.jmap.limits.match-variables")?
|
||||
.property("sieve.untrusted.limits.match-variables")?
|
||||
.unwrap_or(30),
|
||||
)
|
||||
.with_max_local_variables(
|
||||
config
|
||||
.property("sieve.jmap.limits.local-variables")?
|
||||
.property("sieve.untrusted.limits.local-variables")?
|
||||
.unwrap_or(128),
|
||||
)
|
||||
.with_max_header_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.header-size")?
|
||||
.property("sieve.untrusted.limits.header-size")?
|
||||
.unwrap_or(1024),
|
||||
)
|
||||
.with_max_includes(config.property("sieve.jmap.limits.includes")?.unwrap_or(3)),
|
||||
.with_max_includes(
|
||||
config
|
||||
.property("sieve.untrusted.limits.includes")?
|
||||
.unwrap_or(3),
|
||||
),
|
||||
sieve_runtime: Runtime::new()
|
||||
.with_max_nested_includes(
|
||||
config
|
||||
.property("sieve.jmap.limits.nested-includes")?
|
||||
.property("sieve.untrusted.limits.nested-includes")?
|
||||
.unwrap_or(3),
|
||||
)
|
||||
.with_cpu_limit(config.property("sieve.jmap.limits.cpu")?.unwrap_or(5000))
|
||||
.with_cpu_limit(
|
||||
config
|
||||
.property("sieve.untrusted.limits.cpu")?
|
||||
.unwrap_or(5000),
|
||||
)
|
||||
.with_max_variable_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.variable-size")?
|
||||
.property("sieve.untrusted.limits.variable-size")?
|
||||
.unwrap_or(4096),
|
||||
)
|
||||
.with_max_redirects(config.property("sieve.jmap.limits.redirects")?.unwrap_or(1))
|
||||
.with_max_redirects(
|
||||
config
|
||||
.property("sieve.untrusted.limits.redirects")?
|
||||
.unwrap_or(1),
|
||||
)
|
||||
.with_max_received_headers(
|
||||
config
|
||||
.property("sieve.jmap.limits.received-headers")?
|
||||
.property("sieve.untrusted.limits.received-headers")?
|
||||
.unwrap_or(10),
|
||||
)
|
||||
.with_max_header_size(
|
||||
config
|
||||
.property("sieve.jmap.limits.header-size")?
|
||||
.property("sieve.untrusted.limits.header-size")?
|
||||
.unwrap_or(1024),
|
||||
)
|
||||
.with_max_out_messages(
|
||||
config
|
||||
.property("sieve.jmap.limits.outgoing-messages")?
|
||||
.property("sieve.untrusted.limits.outgoing-messages")?
|
||||
.unwrap_or(3),
|
||||
)
|
||||
.with_default_vacation_expiry(
|
||||
config
|
||||
.property::<Duration>("sieve.jmap.default-expiry.vacation")?
|
||||
.property::<Duration>("sieve.untrusted.default-expiry.vacation")?
|
||||
.unwrap_or(Duration::from_secs(30 * 86400))
|
||||
.as_secs(),
|
||||
)
|
||||
.with_default_duplicate_expiry(
|
||||
config
|
||||
.property::<Duration>("sieve.jmap.default-expiry.duplicate")?
|
||||
.property::<Duration>("sieve.untrusted.default-expiry.duplicate")?
|
||||
.unwrap_or(Duration::from_secs(7 * 86400))
|
||||
.as_secs(),
|
||||
)
|
||||
.without_capabilities(
|
||||
config
|
||||
.values("sieve.jmap.disable-capabilities")
|
||||
.values("sieve.untrusted.disable-capabilities")
|
||||
.map(|(_, v)| v),
|
||||
)
|
||||
.with_valid_notification_uris({
|
||||
let values = config
|
||||
.values("sieve.jmap.notification-uris")
|
||||
.values("sieve.untrusted.notification-uris")
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
if !values.is_empty() {
|
||||
|
@ -330,7 +342,7 @@ impl JMAP {
|
|||
})
|
||||
.with_protected_headers({
|
||||
let values = config
|
||||
.values("sieve.jmap.protected-headers")
|
||||
.values("sieve.untrusted.protected-headers")
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
if !values.is_empty() {
|
||||
|
@ -346,13 +358,13 @@ impl JMAP {
|
|||
})
|
||||
.with_vacation_default_subject(
|
||||
config
|
||||
.value("sieve.jmap.vacation.default-subject")
|
||||
.value("sieve.untrusted.vacation.default-subject")
|
||||
.unwrap_or("Automated reply")
|
||||
.to_string(),
|
||||
)
|
||||
.with_vacation_subject_prefix(
|
||||
config
|
||||
.value("sieve.jmap.vacation.subject-prefix")
|
||||
.value("sieve.untrusted.vacation.subject-prefix")
|
||||
.unwrap_or("Auto: ")
|
||||
.to_string(),
|
||||
)
|
||||
|
|
|
@ -95,7 +95,7 @@ impl ConfigSieve for Config {
|
|||
.with_max_header_size(10240)
|
||||
.with_max_includes(10)
|
||||
.with_no_capability_check(
|
||||
self.property_or_static("sieve.smtp.no-capability-check", "false")?,
|
||||
self.property_or_static("sieve.trusted.no-capability-check", "false")?,
|
||||
)
|
||||
.register_functions(&mut fnc_map);
|
||||
|
||||
|
@ -115,32 +115,32 @@ impl ConfigSieve for Config {
|
|||
.with_capability(Capability::Expressions)
|
||||
.with_capability(Capability::While)
|
||||
.with_max_variable_size(
|
||||
self.property_or_static("sieve.smtp.limits.variable-size", "52428800")?,
|
||||
self.property_or_static("sieve.trusted.limits.variable-size", "52428800")?,
|
||||
)
|
||||
.with_max_header_size(10240)
|
||||
.with_valid_notification_uri("mailto")
|
||||
.with_valid_ext_lists(ctx.directory.lookups.keys().map(|k| k.to_string()))
|
||||
.with_functions(&mut fnc_map);
|
||||
|
||||
if let Some(value) = self.property("sieve.smtp.limits.redirects")? {
|
||||
if let Some(value) = self.property("sieve.trusted.limits.redirects")? {
|
||||
runtime.set_max_redirects(value);
|
||||
}
|
||||
if let Some(value) = self.property("sieve.smtp.limits.out-messages")? {
|
||||
if let Some(value) = self.property("sieve.trusted.limits.out-messages")? {
|
||||
runtime.set_max_out_messages(value);
|
||||
}
|
||||
if let Some(value) = self.property("sieve.smtp.limits.cpu")? {
|
||||
if let Some(value) = self.property("sieve.trusted.limits.cpu")? {
|
||||
runtime.set_cpu_limit(value);
|
||||
}
|
||||
if let Some(value) = self.property("sieve.smtp.limits.nested-includes")? {
|
||||
if let Some(value) = self.property("sieve.trusted.limits.nested-includes")? {
|
||||
runtime.set_max_nested_includes(value);
|
||||
}
|
||||
if let Some(value) = self.property("sieve.smtp.limits.received-headers")? {
|
||||
if let Some(value) = self.property("sieve.trusted.limits.received-headers")? {
|
||||
runtime.set_max_received_headers(value);
|
||||
}
|
||||
if let Some(value) = self.property::<Duration>("sieve.smtp.limits.duplicate-expiry")? {
|
||||
if let Some(value) = self.property::<Duration>("sieve.trusted.limits.duplicate-expiry")? {
|
||||
runtime.set_default_duplicate_expiry(value.as_secs());
|
||||
}
|
||||
let hostname = if let Some(hostname) = self.value("sieve.smtp.hostname") {
|
||||
let hostname = if let Some(hostname) = self.value("sieve.trusted.hostname") {
|
||||
hostname
|
||||
} else {
|
||||
self.value_require("server.hostname")?
|
||||
|
@ -148,13 +148,13 @@ impl ConfigSieve for Config {
|
|||
runtime.set_local_hostname(hostname.to_string());
|
||||
|
||||
// Parse scripts
|
||||
for id in self.sub_keys("sieve.smtp.scripts") {
|
||||
let key = ("sieve.smtp.scripts", id);
|
||||
for id in self.sub_keys("sieve.trusted.scripts") {
|
||||
let key = ("sieve.trusted.scripts", id);
|
||||
|
||||
let script = if !self.contains_key(key) {
|
||||
let mut script = Vec::new();
|
||||
for sub_key in self.sub_keys(key) {
|
||||
script.extend(self.file_contents(("sieve.smtp.scripts", id, sub_key))?);
|
||||
script.extend(self.file_contents(("sieve.trusted.scripts", id, sub_key))?);
|
||||
}
|
||||
script
|
||||
} else {
|
||||
|
@ -172,14 +172,14 @@ impl ConfigSieve for Config {
|
|||
|
||||
// Parse DKIM signatures
|
||||
let mut sign = Vec::new();
|
||||
for (pos, id) in self.values("sieve.smtp.sign") {
|
||||
for (pos, id) in self.values("sieve.trusted.sign") {
|
||||
if let Some(dkim) = ctx.signers.get(id) {
|
||||
sign.push(dkim.clone());
|
||||
} else {
|
||||
return Err(format!(
|
||||
"No DKIM signer found with id {:?} for key {:?}.",
|
||||
id,
|
||||
("sieve.smtp.sign", pos).as_key()
|
||||
("sieve.trusted.sign", pos).as_key()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -190,15 +190,15 @@ impl ConfigSieve for Config {
|
|||
lookup: ctx.directory.lookups.clone(),
|
||||
config: SieveConfig {
|
||||
from_addr: self
|
||||
.value("sieve.smtp.from-addr")
|
||||
.value("sieve.trusted.from-addr")
|
||||
.map(|a| a.to_string())
|
||||
.unwrap_or(format!("MAILER-DAEMON@{hostname}")),
|
||||
from_name: self
|
||||
.value("sieve.smtp.from-name")
|
||||
.value("sieve.trusted.from-name")
|
||||
.unwrap_or("Mailer Daemon")
|
||||
.to_string(),
|
||||
return_path: self
|
||||
.value("sieve.smtp.return-path")
|
||||
.value("sieve.trusted.return-path")
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
sign,
|
||||
|
|
|
@ -1,8 +1,45 @@
|
|||
#############################################
|
||||
# SMTP Sieve interpreter configuration
|
||||
# Sieve untrusted runtime configuration
|
||||
#############################################
|
||||
|
||||
[sieve.smtp]
|
||||
[sieve.untrusted]
|
||||
disable-capabilities = []
|
||||
notification-uris = ["mailto"]
|
||||
protected-headers = ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]
|
||||
|
||||
[sieve.untrusted.limits]
|
||||
name-length = 512
|
||||
max-scripts = 256
|
||||
script-size = 102400
|
||||
string-length = 4096
|
||||
variable-name-length = 32
|
||||
variable-size = 4096
|
||||
nested-blocks = 15
|
||||
nested-tests = 15
|
||||
nested-foreverypart = 3
|
||||
match-variables = 30
|
||||
local-variables = 128
|
||||
header-size = 1024
|
||||
includes = 3
|
||||
nested-includes = 3
|
||||
cpu = 5000
|
||||
redirects = 1
|
||||
received-headers = 10
|
||||
outgoing-messages = 3
|
||||
|
||||
[sieve.untrusted.vacation]
|
||||
default-subject = "Automated reply"
|
||||
subject-prefix = "Auto: "
|
||||
|
||||
[sieve.untrusted.default-expiry]
|
||||
vacation = "30d"
|
||||
duplicate = "7d"
|
||||
|
||||
#############################################
|
||||
# Sieve trusted runtime configuration
|
||||
#############################################
|
||||
|
||||
[sieve.trusted]
|
||||
from-name = "Automated Message"
|
||||
from-addr = "no-reply@%{DEFAULT_DOMAIN}%"
|
||||
return-path = ""
|
||||
|
@ -10,7 +47,7 @@ return-path = ""
|
|||
no-capability-check = true
|
||||
sign = ["rsa"]
|
||||
|
||||
[sieve.smtp.limits]
|
||||
[sieve.trusted.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
|
@ -18,7 +55,7 @@ cpu = 1048576
|
|||
nested-includes = 5
|
||||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.smtp.scripts]
|
||||
[sieve.trusted.scripts]
|
||||
#connect = '''require ["variables", "extlists", "reject"];
|
||||
# if string :list "${env.remote_ip}" "default/blocked-ips" {
|
||||
# reject "Your IP '${env.remote_ip}' is not welcomed here.";
|
|
@ -11,6 +11,7 @@ base_path = "__BASE_PATH__"
|
|||
files = [ "%{BASE_PATH}%/etc/common/server.toml",
|
||||
"%{BASE_PATH}%/etc/common/tls.toml",
|
||||
"%{BASE_PATH}%/etc/common/tracing.toml",
|
||||
"%{BASE_PATH}%/etc/common/sieve.toml",
|
||||
"%{BASE_PATH}%/etc/directory/sql.toml",
|
||||
"%{BASE_PATH}%/etc/imap/listener.toml",
|
||||
"%{BASE_PATH}%/etc/imap/settings.toml",
|
||||
|
@ -20,7 +21,6 @@ files = [ "%{BASE_PATH}%/etc/common/server.toml",
|
|||
"%{BASE_PATH}%/etc/jmap/protocol.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/push.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/ratelimit.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/sieve.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/store.toml",
|
||||
"%{BASE_PATH}%/etc/jmap/websockets.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/auth.toml",
|
||||
|
@ -31,6 +31,5 @@ files = [ "%{BASE_PATH}%/etc/common/server.toml",
|
|||
"%{BASE_PATH}%/etc/smtp/report.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/resolver.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/session.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/sieve.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/signature.toml",
|
||||
"%{BASE_PATH}%/etc/smtp/spamfilter.toml" ]
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
#############################################
|
||||
# JMAP Sieve interpreter configuration
|
||||
#############################################
|
||||
|
||||
[sieve.jmap]
|
||||
disable-capabilities = []
|
||||
notification-uris = ["mailto"]
|
||||
protected-headers = ["Original-Subject", "Original-From", "Received", "Auto-Submitted"]
|
||||
|
||||
[sieve.jmap.limits]
|
||||
name-length = 512
|
||||
max-scripts = 256
|
||||
script-size = 102400
|
||||
string-length = 4096
|
||||
variable-name-length = 32
|
||||
variable-size = 4096
|
||||
nested-blocks = 15
|
||||
nested-tests = 15
|
||||
nested-foreverypart = 3
|
||||
match-variables = 30
|
||||
local-variables = 128
|
||||
header-size = 1024
|
||||
includes = 3
|
||||
nested-includes = 3
|
||||
cpu = 5000
|
||||
redirects = 1
|
||||
received-headers = 10
|
||||
outgoing-messages = 3
|
||||
|
||||
[sieve.jmap.vacation]
|
||||
default-subject = "Automated reply"
|
||||
subject-prefix = "Auto: "
|
||||
|
||||
[sieve.jmap.default-expiry]
|
||||
vacation = "30d"
|
||||
duplicate = "7d"
|
|
@ -82,7 +82,7 @@ values = "file://%{BASE_PATH}%/etc/spamfilter/maps/spam_trap.list"
|
|||
type = "map"
|
||||
values = "file://%{BASE_PATH}%/etc/spamfilter/maps/scores.map"
|
||||
|
||||
[sieve.smtp.scripts]
|
||||
[sieve.trusted.scripts]
|
||||
spam-filter = ["file://%{BASE_PATH}%/etc/spamfilter/scripts/config.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/prelude.sieve",
|
||||
"file://%{BASE_PATH}%/etc/spamfilter/scripts/from.sieve",
|
||||
|
|
|
@ -27,14 +27,14 @@ use utils::config::Config;
|
|||
use crate::smtp::{TestConfig, TestSMTP};
|
||||
|
||||
const CONFIG: &str = r#"
|
||||
[sieve.smtp]
|
||||
[sieve.trusted]
|
||||
from-name = "Sieve Daemon"
|
||||
from-addr = "sieve@foobar.org"
|
||||
return-path = ""
|
||||
hostname = "mx.foobar.org"
|
||||
no-capability-check = true
|
||||
|
||||
[sieve.smtp.limits]
|
||||
[sieve.trusted.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
|
@ -127,7 +127,7 @@ values = "file://%CFG_PATH%/maps/scores.map"
|
|||
[resolver]
|
||||
public-suffix = "file://%LIST_PATH%/public-suffix.dat"
|
||||
|
||||
[sieve.smtp.scripts]
|
||||
[sieve.trusted.scripts]
|
||||
"#;
|
||||
|
||||
const CREATE_TABLES: &[&str; 3] = &[
|
||||
|
|
|
@ -46,13 +46,13 @@ rewrite = [ { all-of = [ { if = "rcpt-domain", eq = "foobar.net" },
|
|||
script = [ { if = "rcpt-domain", eq = "foobar.org", then = "rcpt" },
|
||||
{ else = false } ]
|
||||
|
||||
[sieve.smtp]
|
||||
[sieve.trusted]
|
||||
from-name = "Sieve Daemon"
|
||||
from-addr = "sieve@foobar.org"
|
||||
return-path = ""
|
||||
hostname = "mx.foobar.org"
|
||||
|
||||
[sieve.smtp.limits]
|
||||
[sieve.trusted.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
|
@ -60,7 +60,7 @@ cpu = 10000
|
|||
nested-includes = 5
|
||||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.smtp.scripts]
|
||||
[sieve.trusted.scripts]
|
||||
mail = '''
|
||||
require ["variables", "envelope"];
|
||||
|
||||
|
|
|
@ -60,14 +60,14 @@ command = [ { if = "remote-ip", eq = "10.0.0.123", then = "/bin/bash" },
|
|||
arguments = ["%CFG_PATH%/pipe_me.sh", "hello", "world"]
|
||||
timeout = "10s"
|
||||
|
||||
[sieve.smtp]
|
||||
[sieve.trusted]
|
||||
from-name = "Sieve Daemon"
|
||||
from-addr = "sieve@foobar.org"
|
||||
return-path = ""
|
||||
hostname = "mx.foobar.org"
|
||||
sign = ["rsa"]
|
||||
|
||||
[sieve.smtp.limits]
|
||||
[sieve.trusted.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
|
@ -75,7 +75,7 @@ cpu = 10000
|
|||
nested-includes = 5
|
||||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.smtp.scripts]
|
||||
[sieve.trusted.scripts]
|
||||
"#;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Reference in a new issue