stalwartlabs / mail-server

Secure & Modern All-in-One Mail Server (IMAP, JMAP, POP3, SMTP)
https://stalw.art
4.45k stars 171 forks source link

[bug]: is_local_domain() function doesn't work in sieve scripts #622

Closed mamiu closed 1 month ago

mamiu commented 1 month ago

What happened?

I'm trying to check if the recipients domain is a local domain in a sieve script (attached to session.data.script).

And I don't want to manually keep the script in sync with local domains like so:

if anyof (
  envelope :domain :is "to" "example1.com",
  envelope :domain :is "to" "example2.com",
  envelope :domain :is "to" "example3.com"
) {
  # do something
}

The is_local_domain() function can do just that and in combination with the eval keyword I thought it should be possible. But no matter what I do, the is_local_domain function doesn't work in sieve scripts (in normal config expressions it does work as expected). BTW: I have one directory with the ID 'internal', of type Internal, and rocksdb as storage backend (the default configuration).

Here are a few things I tried (content of if conditions omitted for brevity):

if eval "is_local_domain('*', 'oneofmylocaldomains.com')" {}
if eval "is_local_domain('internal', 'oneofmylocaldomains.com')" {}
if eval "is_local_domain('default', 'oneofmylocaldomains.com')" {}
if eval "is_local_domain('', 'oneofmylocaldomains.com')" {}

let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
if eval "is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com')" {}

let "DOMAIN_DIRECTORY" "key_get('lookup', 'directory')";
if eval "is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com')" {}

let "DOMAIN_DIRECTORY" "key_get('storage', 'directory')";
if eval "is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com')" {}

let "DOMAIN_DIRECTORY" "key_get('', 'directory')";
if eval "is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com')" {}

How can we reproduce the problem?

I can reproduce the problem by doing the following steps:

  1. Create a sieve script with the following content (replace oneofmylocaldomains.com with a domain on your server):

    eval "add_header('X-XXXXXXXXXXXXXX', '1: ' + is_local_domain('*', 'oneofmylocaldomains.com'))";
    eval "add_header('X-XXXXXXXXXXXXXX', '2: ' + is_local_domain('internal', 'oneofmylocaldomains.com'))";
    eval "add_header('X-XXXXXXXXXXXXXX', '3: ' + is_local_domain('default', 'oneofmylocaldomains.com'))";
    eval "add_header('X-XXXXXXXXXXXXXX', '4: ' + is_local_domain('', 'oneofmylocaldomains.com'))";
    
    let "DOMAIN_DIRECTORY" "key_get('spam-config', 'directory')";
    eval "add_header('X-XXXXXXXXXXXXXX', '5: ' + is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com'))";
    
    let "DOMAIN_DIRECTORY" "key_get('lookup', 'directory')";
    eval "add_header('X-XXXXXXXXXXXXXX', '6: ' + is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com'))";
    
    let "DOMAIN_DIRECTORY" "key_get('storage', 'directory')";
    eval "add_header('X-XXXXXXXXXXXXXX', '7: ' + is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com'))";
    
    let "DOMAIN_DIRECTORY" "key_get('', 'directory')";
    eval "add_header('X-XXXXXXXXXXXXXX', '8: ' + is_local_domain(DOMAIN_DIRECTORY, 'oneofmylocaldomains.com'))";

    The results of the is_local_domain() function is casted to a string because the add_header function doesn't accept numbers as value.

  2. Attach this script to the inbound SMTP pipeline (e.g. the DATA stage session.data.script)

  3. Send an email to any email address with your specified domain (e.g. test@oneofmylocaldomains.com)

  4. Check the header fields of the email

Result:

X-XXXXXXXXXXXXXX: 1: 0
X-XXXXXXXXXXXXXX: 2: 0
X-XXXXXXXXXXXXXX: 3: 
X-XXXXXXXXXXXXXX: 4: 
X-XXXXXXXXXXXXXX: 5: 
X-XXXXXXXXXXXXXX: 6: 
X-XXXXXXXXXXXXXX: 7: 
X-XXXXXXXXXXXXXX: 8: 

Expected result:

At least one of the headers should show a 1 instead of 0.

Version

v0.8.x

What database are you using?

RocksDB

What blob storage are you using?

RocksDB

Where is your directory located?

Internal

What operating system are you using?

Docker

Relevant log output

No response

Code of Conduct

mdecimus commented 1 month ago

Have you tried setting the log level to trace to see if any errors of warning messages show up in the logs?

mamiu commented 1 month ago

These are the only three log entries I get in regards to this sieve script:

DATE LEVEL MESSAGE
Tue, 16 Jul 2024 10:56:22 DEBUG common::scripts::plugins::lookup: context="sieve:key_get" event="failed" reason="Unknown store or lookup id" lookup_id="storage"
Tue, 16 Jul 2024 10:56:22 DEBUG common::scripts::plugins::lookup: context="sieve:key_get" event="failed" reason="Unknown store or lookup id" lookup_id="lookup"
Tue, 16 Jul 2024 10:56:22 WARN common::scripts::plugins::lookup: context="sieve:is_local_domain" event="failed" reason="Unknown directory" lookup_id="default"
mdecimus commented 1 month ago

This looks like a database error that is not being properly reported. I'm currently working on improving the logging mechanism. Once the new version is released (hopefully by the end of next week) we'll test this again.

mamiu commented 1 month ago

Great, thanks. Looking forward to it!