├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── mta-lmdb.pl └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | !Build/ 2 | .last_cover_stats 3 | /META.yml 4 | /META.json 5 | /MYMETA.* 6 | *.o 7 | *.pm.tdy 8 | *.bs 9 | 10 | # Devel::Cover 11 | cover_db/ 12 | 13 | # Devel::NYTProf 14 | nytprof.out 15 | 16 | # Dizt::Zilla 17 | /.build/ 18 | 19 | # Module::Build 20 | _build/ 21 | Build 22 | Build.bat 23 | 24 | # Module::Install 25 | inc/ 26 | 27 | # ExtUtils::MakeMaker 28 | /blib/ 29 | /_eumm/ 30 | /*.gz 31 | /Makefile 32 | /Makefile.old 33 | /MANIFEST.bak 34 | /pm_to_blib 35 | /*.zip 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jeff Forsyth 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /mta-lmdb.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | 4 | 5 | 6 | 7 | 8 | # The full power of MTA-STS 9 | 10 | # MIT License 11 | 12 | # Copyright (c) 2019 Jeff Forsyth 13 | 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | # getmta is a subroutine that performs a MTA-STS lookup. 40 | # This system will return a mode and a return value. 41 | # The mode can be dane, none, enforce, testing or fail. 42 | # If the result is enforce or testing, the return value will contain allowed mx servers. 43 | 44 | sub getmta 45 | { 46 | 47 | use strict; 48 | use LMDB_File qw(:flags :cursor_op); 49 | use File::Path qw(make_path remove_tree); 50 | use Mail::STS; 51 | use DateTime; 52 | use List::Util qw (any); 53 | my $domainname = lc shift; 54 | my $hostname = lc shift; 55 | 56 | my $TimeEpoch = DateTime->now()->epoch; 57 | my $path = '/var/spool/exim4/lmdb2'; 58 | 59 | # Fail quickly if there is no database or access to the database. 60 | eval { make_path( $path ); 1} or do { return ('fail'); }; 61 | 62 | my $env = LMDB::Env->new($path, { 63 | mapsize => 100 * 1024 * 1024 * 1024, 64 | mode => 0660, 65 | }); 66 | 67 | my $txn = $env->BeginTxn(); 68 | if (! $hostname) { 69 | # Setup the vars and Mail::STS 70 | 71 | my $sts = Mail::STS->new; 72 | my $domain = $sts->domain($domainname); 73 | 74 | my ($DANE,$STSID, $Output, $policy, $domainReport, $policyMX, $policyMODE, 75 | $policyMAX, $policyMXArray, $ExpireTime, $idfromDB, $STSID, $BADID ); 76 | 77 | # Stop processing quickly if the domain has a TLSA record that identifies as DANE 78 | eval { $DANE = $domain->tlsa; }; 79 | if ( $DANE ) { 80 | $txn->commit; 81 | # Dane is primary. Exit out and depend on Dane 82 | return ('dane') 83 | ;} 84 | 85 | # Set the default of all good 86 | my $PolicyinDB = 1; 87 | my $PolicyinDBgood = 1; 88 | 89 | # Setup the Databases 90 | 91 | my $mtaDB = $txn->OpenDB( { # Open/Create a database for the Policy ID 92 | flags => MDB_CREATE 93 | }); 94 | 95 | # Use the Expiration time to check if the record in the db is proper. Then check to see if it is expired. 96 | eval { 97 | $ExpireTime = $mtaDB->get("${domainname}:expire"); 98 | $PolicyinDBgood = ($ExpireTime > $TimeEpoch) ? 1:0; 99 | } or do { $PolicyinDB = 0; }; 100 | 101 | # Use the STS id from the DNS to determind if there is an MTS-STS DNS Record 102 | eval { 103 | $domain->sts; 104 | $STSID = $domain->sts->id; 105 | } or do { $STSID = 0 }; 106 | 107 | 108 | # Only if there is a Policy in the DB and the MTA-STS version in the DNS is valid, check if the version numbers are the same. 109 | # If not, reload the policy in the DB. 110 | if ( $PolicyinDB ) { 111 | $idfromDB = $mtaDB->get("${domainname}:id"); 112 | if ( $STSID ) { 113 | if ($idfromDB != $STSID) { $PolicyinDBgood = 0 } 114 | } 115 | } 116 | 117 | # No Policy in the DB or the policy in the DB is bad, load the policy into the DB only if the MTA-STS version in the DNS is valid. 118 | # Fail if the Policy can't be read 119 | if ( (!$PolicyinDB || !$PolicyinDBgood) && $STSID ) { 120 | eval { $policy = $domain->policy; } or do { 121 | $txn->commit; 122 | # Found a good MTA-STS DNS entry and no policy is available from the cache or http 123 | return ('fail'); 124 | }; 125 | # Check for badly formatted Policy 126 | eval { 127 | $policyMXArray = $policy->mx; 128 | $policyMX = "@$policyMXArray"; 129 | $policyMX =~s/ /:/g; 130 | $policyMODE = $policy->mode; 131 | $policyMAX = ($policy->max_age + $TimeEpoch); 132 | } or do { 133 | $txn->commit; 134 | # Found some aspects of a policy, but it is badly formatted 135 | return ('fail'); 136 | }; 137 | 138 | eval { $domainReport = substr($domain->tlsrpt->rua,7); }; 139 | 140 | $mtaDB->put("${domainname}:mode",$policyMODE,MDB_NODUPDATA); 141 | $mtaDB->put("${domainname}:mx","$policyMX",MDB_NODUPDATA); 142 | $mtaDB->put("${domainname}:expire",$policyMAX,MDB_NODUPDATA); 143 | $mtaDB->put("${domainname}:id",$STSID,MDB_NODUPDATA); 144 | $mtaDB->put("${domainname}:report",$domainReport,MDB_NODUPDATA); 145 | $txn->commit; 146 | # Loaded the DB with a proper policy. Send the current mode and have a nice day. 147 | return "$policyMODE"; 148 | } 149 | 150 | # Policy in the DB is good and it hasn't expired. 151 | if ( $PolicyinDB && $PolicyinDBgood ) { 152 | $policyMODE = $mtaDB->get("${domainname}:mode"); 153 | $policyMX = $mtaDB->get("${domainname}:mx"); 154 | $txn->commit; 155 | # Current policy found in the DB. Send the current mode. 156 | return "$policyMODE"; 157 | } 158 | 159 | # No Policy in the DB and there is no MTA-STS record in the DNS. 160 | $txn->commit; 161 | return ('none'); 162 | } else { 163 | 164 | # A hostname has been found in the options. Check to see if the hostname is proper for the domainname. 165 | 166 | my $mtaDB = $txn->OpenDB( { # Open/Create a database for the MX records in the Policy 167 | flags => MDB_CREATE 168 | }); 169 | my $policyMX = $mtaDB->get("${domainname}:mx"); 170 | $txn->commit; 171 | 172 | my %charmap = ( 173 | '.' => '\.', 174 | '*' => '.*', 175 | ':' => ':', 176 | ); 177 | $policyMX =~ s{(.)} { $charmap{$1} || "\Q$1" }ge; 178 | my @HostNameListArray = split /:/, $policyMX; 179 | my $bool; 180 | $bool = any { $hostname=~$_ } @HostNameListArray; 181 | if ($bool) { return (""); } 182 | return (0); 183 | } 184 | } 185 | 186 | # Simply return the MX records in the policy for limiting in EXIM 187 | sub getmx 188 | { 189 | use strict; 190 | use LMDB_File qw(:flags :cursor_op); 191 | my $domainname = lc shift; 192 | my $path = '/var/spool/exim4/lmdb2'; 193 | 194 | # Fail quickly if there is no database or access to the database. 195 | eval { make_path( $path ); 1} or do { return ('fail'); }; 196 | 197 | my $env = LMDB::Env->new($path, { 198 | mapsize => 100 * 1024 * 1024 * 1024, 199 | mode => 0660, 200 | }); 201 | 202 | my $txn = $env->BeginTxn(); 203 | my $mtaDB = $txn->OpenDB( { # Open/Create a database for the MX records in the Policy 204 | flags => MDB_CREATE 205 | }); 206 | 207 | my $mxReturn = $mtaDB->get("${domainname}:mx"); 208 | $txn->commit; 209 | return ($mxReturn); 210 | } 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MTASTS-EXIM-PERL 2 | ## Perl script designed to be used by Exim for RFC 8461 MTA-STS compliance. 3 | 4 | ### Perl Dependancies: 5 | ``` 6 | File::Path 7 | DateTime 8 | List::Util 9 | cpan: Mail::STS 10 | LMDB_File 11 | ``` 12 | 13 | 14 | This script is designed to work with the Exim Perl interpreter. 15 | On demand, this script will check if MTA-STS data is in a LMDB database. If it is not then it will poll a domain for MTA-STS info and put the info into the database. Then, respond to EXIM with required info for processing the outgoing email. 16 | 17 | This script provides reboot resistant caching of MTA-STS data. And if the database is not found, it will reconstruct the database and restart the caching. 18 | 19 | There are two different subroutines: 20 | 21 | ### getmta: 22 | #### getmta (DomainName) (HostName) 23 | If the HostName is not provided, this script will check the MTA-STS dns record and HTTP record. And will put the MTA-STS record into an LMDB database. The return will be "enforce", "testing", "none", "dane" or "fail". A "fail" means that there is a problem with the MTA-STS information. 24 | 25 | If the Domain name is not in the database, the MTA-STS dns and http will be polled and checked. The data will be placed into the LMDB database. 26 | 27 | If the Domain name is in the database, the expiration of the info will be checked. If the info has expired, it will attempt to get a new record and put the data into the database. 28 | 29 | When the HostName is present, the script will check the LMDB database to determine if the hostname is within the MTA-STS mx records. If it is not the return is a zero ("0"). Else nothing is returned. 30 | 31 | ### getmx: 32 | #### getmx (domainname) 33 | Returns the MX list from the MTA-STS record as a colon seperated list. 34 | 35 | Per RFC 8461, testing allows for an mta-sts failure. So, this will only be logged at EXIM. In the future, this can be used with the TLSRPT feature to provide a report to the server admin. 36 | 37 | ### LMDB Info 38 | This database was chosen due to the speed and concurrency. It is a Key-Value store system. 39 | There are no named databases within the LMDB structure. 40 | 41 | Any software that can access LMDB databse shouldn't have a problem accessing the data provided the application has rights. 42 | 43 | The data structue is as follows: 44 | 45 | - domainname.tld:mx 46 | - Contains a colon seperated list of domain names listed in the MTA-STS HTML record. This may contain wildcards. 47 | - domainname.tld:mode 48 | - Contains the mode the MTA-STS record is in. This may be one of the following "enforce","testing" or "none" 49 | - domainname.tld:expire 50 | - This contains the Unix Time that this record expires in seconds. 51 | - domainname.tld:id 52 | - This is the MTA-STS id located in the MTA-STS DNS TXT record. 53 | - domainname.tld:report 54 | - This is the contact URL for the RFC 8460 TLSRPT. When it comes to the MTA-STS RFC 8461, this is only required if the mode/policy is set to "testing" and the sending server sends regular reports. At this time, the mailto: gets stripped and this only contains an email address. 55 | 56 | ### A few notes: 57 | Ensure the path variable is set to someplace your Exim can read/write. In my case it's /var/spool/exim4. 58 | 59 | Gmail.com works perfectly as "enforced". 60 | 61 | Outlook.com and Office365.com works as "testing" without a TLSRPT. 62 | 63 | Yahoo.com works as "testing" with a TLSRPT. 64 | 65 | NBC.com seems to use a wildcard in their DNS server to allow *.nbc.com TXT to respond with "inbound". It may create failures in the MAIL::STS module. Can someone fix Comcast/NBC? 66 | 67 | My distros LWP defaults to the system root certificates. This may need to be adjusted for your system. 68 | 69 | Exim has a an experimental LMDB lookup. This may reduce the need of the getmx script. 70 | 71 | 72 | ### Exim Configs: 73 | ``` 74 | perl_startup = do '(Path of script)/mta-lmdb.pl' 75 | perl_at_start = true 76 | ``` 77 | #### Example Exim Routers: 78 | 79 | ``` 80 | dnslookup_mtasts_enforce: 81 | debug_print = "R: dnslookup-mtasts-enforce for $local_part@$domain" 82 | driver = dnslookup 83 | # This condition uses the getmta subroutine and returns the MTA-STS policy. If the policy is enforce continue with this router. 84 | condition = ${if eq{${perl{getmta}{$domain}}}{enforce}} 85 | domains = ! +local_domains 86 | # Push the mail to the remote_smtp_mtasts_enforce transport 87 | transport = remote_smtp_mtasts_enforce 88 | same_domain_copy_routing = yes 89 | # ignore private rfc1918 and APIPA addresses 90 | ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : 192.168.0.0/16 :\ 91 | 172.16.0.0/12 : 10.0.0.0/8 : 169.254.0.0/16 :\ 92 | 255.255.255.255 93 | dnssec_request_domains = * 94 | no_more 95 | ``` 96 | ``` 97 | dnslookup_mtasts_testing: 98 | debug_print = "R: dnslookup-mtasts-testing for $local_part@$domain" 99 | driver = dnslookup 100 | # This condition uses the getmta subroutine and returns the MTA-STS policy. If the policy is testing continue with this router. 101 | condition = ${if eq{${perl{getmta}{$domain}}}{testing}} 102 | domains = ! +local_domains 103 | # Push the email to the remote_smtp_mtasts_testing transport 104 | transport = remote_smtp_mtasts_testing 105 | same_domain_copy_routing = yes 106 | # ignore private rfc1918 and APIPA addresses 107 | ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : 192.168.0.0/16 :\ 108 | 172.16.0.0/12 : 10.0.0.0/8 : 169.254.0.0/16 :\ 109 | 255.255.255.255 110 | dnssec_request_domains = * 111 | no_more 112 | ``` 113 | ``` 114 | redirect_mtasts_fail: 115 | debug_print = "R: redirect-mtasts-failure for $local_part@$domain $address_data" 116 | driver = redirect 117 | # This condition uses the getmta subroutine and returns the MTA-STS policy. If the policy is fail continue with this router. 118 | condition = ${if eq{${perl{getmta}{$domain}}}{fail}} 119 | domains = ! +local_domains 120 | # Per RFC 8461 defer for another attempt later. Hopefully the receiving agency will fix their MTA-STS. 121 | allow_defer 122 | data = :defer: MTA-STS Failure $address_data 123 | no_more 124 | ``` 125 | 126 | #### Example Transports (Modified from Debian): 127 | ``` 128 | remote_smtp_mtasts_enforce: 129 | debug_print = "T: remote_smtp_mtasts_enforce for $local_part@$domain" 130 | driver = smtp 131 | # Do a full cert check on the MTA-STS mx host names 132 | tls_verify_cert_hostnames = {${perl{getmx}{$domain}}} 133 | tls_tempfail_tryclear = false 134 | # Do not connect to any servers that are not listed in the MTA-STS mx. 135 | event_action = ${if eq {tcp:connect}{$event_name}{${perl{getmta}{$domain}{$host}}} {}} 136 | .ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT 137 | message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} 138 | .endif 139 | .ifdef REMOTE_SMTP_HOSTS_AVOID_TLS 140 | hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS 141 | .endif 142 | .ifdef REMOTE_SMTP_HEADERS_REWRITE 143 | headers_rewrite = REMOTE_SMTP_HEADERS_REWRITE 144 | .endif 145 | .ifdef REMOTE_SMTP_RETURN_PATH 146 | return_path = REMOTE_SMTP_RETURN_PATH 147 | .endif 148 | .ifdef REMOTE_SMTP_HELO_DATA 149 | helo_data=REMOTE_SMTP_HELO_DATA 150 | .endif 151 | .ifdef DKIM_DOMAIN 152 | dkim_domain = DKIM_DOMAIN 153 | .endif 154 | .ifdef DKIM_SELECTOR 155 | dkim_selector = DKIM_SELECTOR 156 | .endif 157 | .ifdef DKIM_PRIVATE_KEY 158 | dkim_private_key = DKIM_PRIVATE_KEY 159 | .endif 160 | .ifdef DKIM_CANON 161 | dkim_canon = DKIM_CANON 162 | .endif 163 | .ifdef DKIM_STRICT 164 | dkim_strict = DKIM_STRICT 165 | .endif 166 | .ifdef DKIM_SIGN_HEADERS 167 | dkim_sign_headers = DKIM_SIGN_HEADERS 168 | .endif 169 | .ifdef TLS_DH_MIN_BITS 170 | tls_dh_min_bits = TLS_DH_MIN_BITS 171 | .endif 172 | .ifdef REMOTE_SMTP_TLS_CERTIFICATE 173 | tls_certificate = REMOTE_SMTP_TLS_CERTIFICATE 174 | .endif 175 | .ifdef REMOTE_SMTP_PRIVATEKEY 176 | tls_privatekey = REMOTE_SMTP_PRIVATEKEY 177 | .endif 178 | .ifndef REMOTE_SMTP_DISABLE_DANE 179 | dnssec_request_domains = * 180 | hosts_try_dane = * 181 | .endif 182 | ``` 183 | ``` 184 | remote_smtp_mtasts_testing: 185 | # This is just a duplicate of normal sending. Per RFC 8461, Don't defer or delay if an MTA-STS deivery has failed if the policy is testing. 186 | # So, don't do anything. This would be the place for a reporting mechanism. 187 | debug_print = "T: remote_smtp_mtasts_testing for $local_part@$domain" 188 | driver = smtp 189 | .ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT 190 | message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}} 191 | .endif 192 | .ifdef REMOTE_SMTP_HOSTS_AVOID_TLS 193 | hosts_avoid_tls = REMOTE_SMTP_HOSTS_AVOID_TLS 194 | .endif 195 | .ifdef REMOTE_SMTP_HEADERS_REWRITE 196 | headers_rewrite = REMOTE_SMTP_HEADERS_REWRITE 197 | .endif 198 | .ifdef REMOTE_SMTP_RETURN_PATH 199 | return_path = REMOTE_SMTP_RETURN_PATH 200 | .endif 201 | .ifdef REMOTE_SMTP_HELO_DATA 202 | helo_data=REMOTE_SMTP_HELO_DATA 203 | .endif 204 | .ifdef DKIM_DOMAIN 205 | dkim_domain = DKIM_DOMAIN 206 | .endif 207 | .ifdef DKIM_SELECTOR 208 | dkim_selector = DKIM_SELECTOR 209 | .endif 210 | .ifdef DKIM_PRIVATE_KEY 211 | dkim_private_key = DKIM_PRIVATE_KEY 212 | .endif 213 | .ifdef DKIM_CANON 214 | dkim_canon = DKIM_CANON 215 | .endif 216 | .ifdef DKIM_STRICT 217 | dkim_strict = DKIM_STRICT 218 | .endif 219 | .ifdef DKIM_SIGN_HEADERS 220 | dkim_sign_headers = DKIM_SIGN_HEADERS 221 | .endif 222 | .ifdef TLS_DH_MIN_BITS 223 | tls_dh_min_bits = TLS_DH_MIN_BITS 224 | .endif 225 | .ifdef REMOTE_SMTP_TLS_CERTIFICATE 226 | tls_certificate = REMOTE_SMTP_TLS_CERTIFICATE 227 | .endif 228 | .ifdef REMOTE_SMTP_PRIVATEKEY 229 | tls_privatekey = REMOTE_SMTP_PRIVATEKEY 230 | .endif 231 | .ifndef REMOTE_SMTP_DISABLE_DANE 232 | dnssec_request_domains = * 233 | hosts_try_dane = * 234 | .endif 235 | ``` 236 | 237 | --------------------------------------------------------------------------------