How do I set up SmarterMail Configuration (Windows 2012) to accept incoming messages only from EuropeanMX servers?



Windows Server 2012 has reached the official End-of-Life (EOL) at Microsoft on October 10, 2023 and Microsoft has discontinued support for this product. The following instructions were correct at the time of writing. However, as this version has reached the end of official support, we can no longer guarantee the success of the configuration.

You can restrict the delivery of messages to your Windows server with the following Powershell script, so that only messages that are to be delivered by EuropeanMX after filtering are accepted. Once the script has been set up, we recommend that you run it every hour using the Windows Task Scheduler. 

# *****************************************************************
#  PURPOSE:
#
#  Look up all IPs for the EuropeanMX primary delivery host name
#  and (re)create windows firewall inbound and outbound rules on
#  specified port. Creates multiple rules, 100 IPs per rule for
#  performance and easier handling.  Only test with IPv4 addresses.
#  
#  Using this script would allow you to restrict inbound traffic 
#  on your windows-based email server, allowing only EuropeanMX
#  servers to connect and drop email. Combined with disallowing
#  traffic on standard SMTP ports and using a non-standard port
#  for EuropeanMX routes, your server will only see traffic
#  that has been filtered for spam content by the EuropeanMX 
#  cloud.

#  Recommended use once configured and working is to use Windows
#  Task Scheduler to run this script once per hour.
#
#  NO WARRANTIES, USE AT YOUR OWN RISK, YADDA YADDA - author
#  not responsible for anything that breaks, gets sick, dies,
#  or gets hacked as a result of using this code.  If you find
#  a way to optimize the code or make it more secure, feel free
#  to report back.
#
#  SETUP:
#  
#  Place this script, and the manual IP list file, in your
#  server's program files directory, or anywhere you wish.
#  Start out by updating the list of IPs in the manual IP file:
#  IP addresses can be obtained from https://www.eunetic.com/mxstatus
#  copy and paste so that there's only ONE IP
#  address per line, using the IPv4 addresses only.
#  Then set variable $useDnsLookup to 0.  Execute the script (you may
#  need elevated privileges) and determine if the firewall rules
#  are created using the IPs in the manual IPs file.
#
#  If your rules are created properly, delete them, then set
#  $useDnsLookup to 1 and re-run to make sure your rules are created
#  properly again.  If so, schedule this script to run once per hour,
#  or more frequently if you wish the window of time to be smaller
#  where email could be delayed due to IP address changes by EuropeanMX.
#  
#  TROUBLESHOOTING:
#
#  (1) Use nslookup at a Windows Command Prompt to use your preferred DNS
#  server to look up EuropeanMX preferred host name. You should get back
#  a full list of IPv4 and IPv6 addresses (56 at the time this file was
#  last updated).  If nslookup doesn't work on your system, it's likely 
#  that the powershell lookup command used here won't work either.
#
#  (2) Review the contents of the execution log and the IP address log files
#  to see what might be going wrong.  Execution log is cumulative, and there
#  should be a file for each DNS lookup, containing all IPv4 addresses,
#  if the script is able to get that far.
#
# *****************************************************************

# *****************************************************************
# CONFIGURATION - safe to edit within this block.
# *****************************************************************

# edit this host name only if directed by EuropeanMX
$europeanmxHostName = "delivery.antispamcloud.com"

# Default is Google Public DNS. You may specify and DNS server IP address or host name here
$dnsServer = "8.8.8.8"

# change this only if you want the date stamp used to name the generated files to look different
$DateStamp = get-date -uformat "%Y%m%d%H%M%S"

# change this only if you want the full name of generated files to look different.
$ipLogFileName = "EuropeanMXIPs-$DateStamp.txt"

# change this if you want to use a different name for the file containing a manually specified list of IPs
$manualIpLogFileName = "EuropeanMXIPsManual.txt"

# change this if you want the generated rules to have a different base name.
$ruleName = "_ScriptedRule - EuropeanMX SMTP Access"

# change this if you want the generated rules to have a different description (or none)
$ruleDescription = "Rule created by script on $(get-date). Do not edit rule by hand, it will be overwritten when the script is run again."

# change this if you wish to use a different port to isolate for delivery by EuropeanMX only
$portNumber = "26"

# change this if you wish to specify a profile type for the firewall rules. (see netsh.exe help)
$profileType = "any"

# change this if you wish to specify an interface type for the firewall rules. (see netsh.exe help)
$interfaceType = "any"

# change this if you wish the firewall rule to apply to only specific IP Address(es) on your server. (see netsh.exe help)
$localIp = "any"

# change this if you want to configure greater or fewer IP addresses per generated rule.
# this feature is to prevent too many IP addresses from being piled into one rule, which
# has been proven to cause myriad problems.  Generally EuropeanMX will probably never have more
# than 100 IP addresses anyway.
$maxIpsPerRule = 100

# This switch determines whether the script will use a DNS lookup (1) or your local manual file (0)
$useDnsLookup = 1

# This is the number of IP list files to keep
$maxLogFiles = 24


# *****************************************************************
# DO NOT EDIT BELOW THIS LINE unless you know what you're doing.
# Improper coding could theoretically lead to undelivered mail from
# EuropeanMX at best, and security-threatening holes in your 
# Windows Firewall at worst. Here be dragons.
# *****************************************************************

# Set up execution log

$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"
Start-Transcript -path .\execution_log.txt -append

$(get-date)

# GET IPs FOR EUROPEANMX, USING EITHER DNS LOOKUP OR MANUAL FILE
# *****************************************************************

if ($useDnsLookup) {

  "`n`nLogging IPs for $europeanmxHostName ..."

  try {

        Resolve-DnsName $europeanmxHostName -Server $dnsServer | %{$_.IPAddress.trim()} | Out-File $ipLogFileName
  }

    catch {
        "`n`nError occurred while resolving EuropeanMX Host Name; processing cannot continue."
        exit
  }    

  "`n`nDNS Lookups completed; Results stored in $ipLogFileName"

} else {

    $ipLogFileName = $manualIpLogFileName
  "`n`nUsing manual IP Log File $ipLogFileName"

}

# BEGIN FIREWALL UPDATE
# *****************************************************************

"`n`nUpdating Windows Firewall using $ipLogFileName..."

# read file containing IP addresses

$file = get-item $ipLogFileName -ErrorAction SilentlyContinue
if (-not $?) { "`n`nCannot find $ipLogFileName, quitting...`n`n" ; exit } #should never happen, really.

# Any existing firewall rules which match the configured name are deleted EVERY time the script runs.
# An alternative to this would be to match and rename existing rules, create new ones, THEN delete the old ones,
# if a truly seamless update to the firewall is desired.  Estimated time that firewall is closed to EuropeanMX is
# currently around 4-5 seconds or so.

"`n`nDeleting any inbound or outbound firewall rules named like '$ruleName-#*'"

$currentRules = netsh.exe advfirewall firewall show rule name=all | select-string '^[Rule Name|Regelname]+:\s+(.+$)' | foreach { $_.matches[0].groups[1].value }

if ($currentRules.count -lt 3) {"`n`nProblem getting a list of current firewall rules, quitting...`n" ; exit }

# Note: If you are getting the above error, editing the regex pattern two lines above to include the 'Rule Name' in your local language could help.

$currentRules | foreach { if ($_ -like "$ruleName-#*"){ netsh.exe advfirewall firewall delete rule name="$_" | out-null } }

# Create array of IP ranges; any line that doesn't start like an IPv4/IPv6 address is ignored.  IPv6 not tested yet; DNS lookup only gets IPv4's anyway.

$ranges = get-content $file | where {($_.trim().length -ne 0) -and ($_ -match '^[0-9a-f]{1,4}[\.\:]')} 
if (-not $?) { "`n`nCould not parse $file, quitting...`n`n" ; exit } 
$lineCount = $ranges.count
if ($lineCount -eq 0) { "`n`nZero IP addresses to add, quitting...`n`n" ; exit }

# Create Rules

$i = 1                     
$start = 1                 
$end = $maxIpsPerRule

do {
    $iCount = $i.tostring().padleft(3,"0")  # Used in name of rule, e.g., RuleName-#042.
    
    if ($end -gt $lineCount) { $end = $lineCount } 
    $textRanges = [System.String]::Join(",",$($ranges[$($start - 1)..$($end - 1)]))    
    
  "`n`nSetting In/Out for $textRanges"     

    "`n`nCreating an  inbound firewall rule named '$ruleName-#$iCount' for IP ranges $start - $end" 
    netsh.exe advfirewall firewall add rule name="$ruleName-#$iCount" dir=in action=allow protocol=tcp localIp="$localIp" localport="$portNumber" remoteip="$textRanges" description="$ruleDescription" profile="$profileType" interfaceType="$interfaceType"
    if (-not $?) { "`nFailed to create '$ruleName-#$icount' inbound rule for some reason, continuing anyway..."}
    
    "`nCreating an outbound firewall rule named '$ruleName-#$icount' for IP ranges $start - $end" 
    netsh.exe advfirewall firewall add rule name="$ruleName-#$icount" dir=out action=allow protocol=tcp localIp="$localIp" localport="$portNumber" remoteip="$textRanges" description="$ruleDescription" profile="$profileType" interfaceType="$interfaceType"
    if (-not $?) { "`n`nFailed to create '$ruleName-#$icount' outbound rule for some reason, continuing anyway..."}
    
    $i++
    $start += $maxIpsPerRule
  $end += $maxIpsPerRule   

} while ($start -le $lineCount)

if ($useDnsLookup) {
    # rename file to include IP count.
    rename-item "$file" "EuropeanMXIPs-$DateStamp-$lineCount-processed.txt"    
}

"`nAll EuropeanMX delivery IPs are now permitted through firewall for SMTP access.`n"

$files = Get-ChildItem -Path "./" | Where-Object {$_.Name -like "EuropeanMXIps-*-processed.txt"} 
if ($files.Count -gt $maxLogFiles) {
    $files | Sort-Object CreationTime | Select-Object -First ($files.Count - $maxLogFiles) | Remove-Item -Force
}

"`n`nRemoved log files older than most recent $maxLogFiles."

Stop-Transcript

Was this article helpful?

No Yes