------------------------------------------------------------------------------- Exim Configuration Exim's architecture has a very clean split between 1/ "ACLs" (Access Control Lists) which control mail acceptance 2/ "Routers" which controls how Exim decides where to deliver messages. ------------------------------------------------------------------------------- Day to Day Working... Queue listing mailq | exiqsumm exim -bp | exiqsumm Force Exim to retry sending all the queued mail exim -qff -v Delete Frozen message exim -Mrm {id} exim -bp | grep frozen | awk '{print $3}' | xargs exim -Mrm exiqgrep -iz | xargs exim -Mrm Though they are automatically removed after a timeout grep timeout_frozen_after /etc/exim.conf Bad mail sources... With recipent zgrep 'external sources' /var/log/exim/reject.log* | sed '/: Mail is not accepted mail from external sources/!d; s///; s/^.* H=// ' | sort | uniq -c | sort -n Without recipent zgrep 'external sources' /var/log/exim/reject.log* | sed '/: Mail is not accepted mail from external sources/!d; s///; s/^.* H=//; s/ RCPT .*/ RCPT/; ' | sort | uniq -c | sort -n ------------------------------------------------------------------------------- Debugging Settings exim -bP [instance] # output the configuration settings! # including those implied or defaults exim -bP -C /dev/null # All default settings Testing without message exim -brw user@domain # Address rewrite for each address location exim -bt user@domain # where the address is to be routed exim -d -bt user@domain # trace the steps of routing a mail addreess # Full trace test... This only works as root! exim -d -DCONFIG_DIR=`pwd` -C `pwd`/exim.conf_test -bt user@domain Message Testing exim -bhc ip_address < msg # run a fake SMTP session this IP exim -odq # recieve message but just queue it exim -M message_id # attempt to deliver a message from queue # messages are thawed and retry counts ignored String Expansion testing ... Only works as root exim -d -be '${lookup{foo}wildlsearch{/etc/aliases}{$value}fail}' ------------------------------------------------------------------------------- exigrep and solaris syslog For solaris syslog you may need to addjust a line in the "exigrep" perl script from if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; } to if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: (\[[^]]+\] )?//o; } ------------------------------------------------------------------------------- Configuration Syntax General whitespace at start and end is ignored, # at start of line is a comment, it is not a comment elsewhere '\' line continuations, white space after this is ignored and comment lines can appear between continued lines Which is useful for comments in file inclusions. File Inclusion .include {file} .include_if_exists {file} On a line by itself, though may be part of a line continuation EG: hosts_lookup = \ .include /etc/exim/host_list.txt Macros... Lines starting with a uppercase is a macro Macro_name = rest_of_line Do not include a macro name inside a macro name EG: this fails when you try to use it ABCD_XYZ = ... ABCD = ... You can redefine a macro for later use ABCD = ... ABCD == ... And Append to macros using ABCD = ... ABCD == ABCD ... Macros can be overridding using -D from command line. Conditional Skips of Configuation File .ifdef ifndef .elifdef .else .endif Condition is true if any macro substition takes place .ifdef AAA message_size_limit = 50M .else message_size_limit = 100M .endif Strings do not need to be quoted, unless escaped charactered like \n \t \{oct} \x{hex} are needed. String Lists Normally colon separated, double colons are used to represent a actual colon. White spaces around each item is ignored. EG: local_interfaces = 127.0.0.1 : ::::1 In this example the spaces are required! Using <{char} changes the separator to that character EG: local_interfaces = <; 127.0.0.1 ; ::1 local_interfaces = "<\n 127.0.0.1 \n ::1" domains = <\n ${lookup mysql{....}} Control characters can not be doubled, and will produce a empty list item Empty List items at the end are ignored, so trailing list items are ignored Eg this has one item senders = user@domain : This list has one empty item senders = : ------------------------------------------------------------------------------- Database lookup NOTE: all keys lookedup in a database is cached to avoid repetion. But only within a single ExMH process. Relevent files are kept open for each process as well. Two types lsearch;/some/file result is the string is found... or not found ${lookup{key}lsearch{/some/file}} result is the data the 'key' refers to in "/some/file" That last can be expanded on to do a single key lookup. ${lookup{key}lsearch{/some/file} Modify the output... ${lookup{key}lsearch{/some/file}{string1}{string2}} On success string1 is expanded and used typically "$value" On failure string2 is expanded and used typically "fail" or "" --- Postfix modification (look for a default)... If '*' is appended to database type EG: lsearch* if initial lookup fails, a look up of '*' is used for a default if '*@' is appened to database type EG: lsearch*@ if initial lookup fails and 'key has a '@' EG: before@after A key search is thus before@after *@after * On partial matches $1 and $2 contains the wild and non-wild parts of the string. ----- Prefix multi key lookups... For dot seperated key lookups (generally domains) if 'partial-' is prepended EG: partial-lsearch then key is split by '.' to try and find a partial match for example for a key of "xyzzy.example.com" it will search xyzzy.example.com *.xyzzy.example.com *.example.com Minimain is 2 words so stops there. You can set that minimum with a number so 'partial3-' would stop with *.bar.example.com while 'partial1-' would stop with *.com You can specify the key prefix of partials using parenthesis For example: partial(.)lsearch will look for keys xyzzy.example.com .xyzzy.example.com .example.com OR: partial1()lsearch has no 'key' prefix and stops at 1 component xyzzy.example.com example.com com a partial0 can be used, but a zero length only works with a key lookup prefix EG: partial0(.)lsearch will look for xyzzy.example.com .xyzzy.example.com .example.com .com . And a final '.' is removed if not empty EG: partial0-lsearch will look for xyzzy.example.com *.xyzzy.example.com *.example.com *.com * The appended '*' or '*@' lookup is done after all partical searches are done. WARNING: a database key of something like *example.com is useless as you must have a '.' after the '*' to find the key in teh database. ----- lsearch literial keys in a 'aliases' like file key: data wildsearch as lsearch but keys in file may start with '*' or begin with '^' to give a regular expression ------------------------------------------------------------------------------- Lookup if user is 'root' aliased exim -be '${local_part: ${lookup{bin}lsearch{'`pwd`'/aliases_users}} }' root How can I use this to prevent 'rewrite' if the local_part is aliased to 'root' ------------------------------------------------------------------------------- Rewrite Addresses Global Address rewriting happens when a message is received. After the ACL's have accepted the mail, but before routing and delivery. If no '@' is in the address being tested the address is against the domains of the email only. Each address is re-written by ALL the matching rules below. The rewrite tests will continue with the rewritten rule, unless the rule is a '*'. ------------------------------------------------------------------------------- Router Examples... --- Domain Forward (without using rewrite of env-to) redirect_old_domain: driver = redirect domains = old.domain.com #local_part = lsearch;/etc/old_users # OPTIONAL: only these users data = ${quote_local_part:$local_part}@new.domain.com # looking up the new username of the users in the new domain # /etc/old_users contains lines of the form; old_user: new_user redirect_old_domain: driver = redirect domains = old.domain.com data = ${lookup{$local_part}lsearch{/etc/old_users}}@new.domain.com # Or a full aliases file... # /etc/old_users contains lines of the form; old_user: new_user@domain redirect_old_domain: driver = redirect domains = old.domain.com data = ${lookup{$local_part}lsearch{/etc/old_users}} ------------------------------------------------------------------------------- "DMARC failure to load tld list" Errors Dispite "control = dmarc_disable_verify" https://exim.org/exim-html-current/doc/html/spec_html/ch-dkim_spf_and_dmarc.html mkdir /usr/share/publicsuffix cd /usr/share/publicsuffix wget https://publicsuffix.org/list/public_suffix_list.dat ------------------------------------------------------------------------------- Exim Rate limiting... NOTE: 22k e-mails per hour averages just six per second! OPTIONS "per_mail" rate of sending mail (default) "per_rcpt" is equivelent to use key=$local_part@$domain "per_byte" is size of mail -- good for 'mails with attachments' "strict" always update the database "leaky" don't update when over limit as client will re-try "readonly" there will be multiple rate-limit checks so don't update Out log of current sender_rate warn ratelimit = 0 / 1h / readonly / $sender_host_address log_message = ratelimit for $sender_host_address is $sender_rate / 1h Log a warning (measuring existing rates to define policy) RATELIMIT=10000 # default rate limit per hour warn ratelimit = ${lookup {$sender_host_address} \ cdb {DB/ratelimits.cdb}{$value}{RATELIMIT}} / strict slow down a fast senders Note this makes it too slow too fast, as delay is in seconds warn ratelimit = ${lookup{$sender_host_name}lsearch*{CONFIG_DIR/rate_limits}} / ➥1h / strict / $sender_host_name message = Sorry, rate limiting mails from $sender_host_name, DELAYING delay = ${eval: ${sg{$sender_rate}{[.].*}{}} - $sender_rate_limit }s log_message = ratelimit $sender_rate to $sender_rate_limit per hour. \ delaying ${eval: ${sg{$sender_rate}{[.].*}{}} - $sender_rate_limit }s Fixed delay... warn ratelimit = ${lookup{$sender_host_name}lsearch*{CONFIG_DIR/rate_limits}} / ➥1h / strict / $sender_host_name message = Sorry, rate limiting mails from $sender_host_name, DELAYING delay = 1s log_message = ratelimit $sender_rate of $sender_rate_limit per hour \ delaying receipt of mail If previous was NOT rate limited print the current rate limit warn condition = ${if <={${sg{$sender_rate}{[.].*}{}}}{$sender_rate_limit} } log_message = ratelimit $sender_rate of $sender_rate_limit per hour Size thoughput... reject authenticated users instead of delay NOTE: connection is not droped defer ratelimit = 4 / 3600 / key=$authenticated_id log_message = RATE CHECK: $sender_rate/$sender_rate_period \ (max $sender_rate_limit) Log checking... File "ratelimit.pl" scans exim logs for specific regex, and determines the peak rate limit for a specific interval. ./ratelimit.pl 3600 ' <= .*? H=(.*?) ' \ <(zcat /var/log/exim/main.log-20210916.gz ) ------------------------------------------------------------------------------- Spam Bot Checks... Spam Bots... which randomise there 'HELO' name on same IP with a helo ok whitelist defer # whitelist checks go here ratelimit = 2.2 / 1w / per_conn / strict \ / unique=$sender_helo_name / $sender_host_address condition = ${if !match_domain{$sender_helo_name}{cdb;DB/helo_ok.cdb} } message = Probable spam bot -- Contact admin log_message = Probable spam bot, HELO varies between $sender_rate domains Spam Bot that uses only randmised uppercase HELO name defer message = Probable spam bot HELO - please try another server condition = ${if and{{ match{$sender_helo_name}{^[A-Z]+\$} } \ {!match_domain{$sender_helo_name}{cdb;DB/helo_ok.cdb} }} } Drop if more than 3 bad recipients. HOW DOES rcpt_fail_count get set? drop message = REJECTED - Too many failed recipients - count = $rcpt_fail_count log_message = REJECTED - Too many failed recipients - count = $rcpt_fail_count condition = ${if > {${eval:$rcpt_fail_count}}{3}{yes}{no}} condition = ${run{/etc/exim/scripts/log-file /var/spool/spam/host-spam.txt $sender_host_address}{yes}{yes}} !verify = recipient/callout=2m,defer_ok,use_sender DNS check defer !verify = helo !hosts = +helo_ok !dnslists = list.dnswl.org Block based on number of bad recipiets per time period https://github.com/Exim/exim/wiki/BlockCracking ------------------------------------------------------------------------------- Throttle (ratelimit) outgoing mail by domain For specific domains send out only 5 messages per connection begin routers remote_domains_throttled: driver = dnslookup domains = \N^yahoo\.\N : rocketmail.com : ymail.com : y7mail.com : \ btinternet.com : btopenworld.com : att.net : sbcglobal.net : \ rogers.com retry_use_local_part transport = throttled_smtp ... begin transports throttled_smtp: driver = smtp connection_max_messages = 5 serialize_hosts = * retry_use_local_part Using a acl to ratelimit outbound... https://github.com/Exim/exim/wiki/RatelimitOutbound -------------------------------------------------------------------------------