├── VERSION ├── .gitattributes ├── .gitignore ├── header.php ├── foot.php ├── functions.php ├── config.php.dist ├── confirm.php ├── head.php ├── README.md ├── edit.php ├── Events ├── PublicSuffixLoad.ps1 ├── EventHandlers.vbs ├── pURIBL-Offline.ps1 └── pURIBLtestScript.vbs ├── login.php ├── index.php ├── domains.php └── stylesheet.css /VERSION: -------------------------------------------------------------------------------- 1 | 7 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore files 2 | /config.php 3 | /Events/test* 4 | 5 | # Ignoring directories 6 | # Both the directory itself and its contents will be ignored. 7 | test/ 8 | ScriptCreatedFiles/ 9 | -------------------------------------------------------------------------------- /header.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /foot.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /config.php.dist: -------------------------------------------------------------------------------- 1 | 'localhost', 30 | 'username' => 'hmailserver', 31 | 'password' => 'supersecretpassword', 32 | 'dbname' => 'hmailserver', 33 | 'tableuriname' => 'hm_puribluri', 34 | 'tabledomname' => 'hm_puribldom', 35 | 'driver' => 'mysql', 36 | 'port' => '3306', 37 | 'dsn' => 'MariaDB ODBC 3.0 Driver' 38 | ); 39 | 40 | 41 | /* Pagination 42 | Number of records per page 43 | */ 44 | 45 | $no_of_records_per_page = 20; 46 | 47 | 48 | ?> -------------------------------------------------------------------------------- /confirm.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | "; 10 | echo "

"; 11 | echo "Black/White List Entry Creation Results:"; 12 | echo "

"; 13 | 14 | $sql = $pdo->prepare(" 15 | SELECT * 16 | FROM ".$Database['tableuriname']." 17 | WHERE id = '".$id."'; 18 | "); 19 | $sql->execute(); 20 | $count = $sql->rowCount(); 21 | if ($count > 0){ 22 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 23 | echo ""; 24 | echo " 25 | 26 | 27 | "; 28 | echo " 29 | 30 | 31 | "; 32 | echo " 33 | 34 | 35 | "; 36 | echo " 37 | 38 | 39 | "; 40 | echo "
ID:".$row['id']."
Trunk:".$row['trunk']."
Branch:".$row['branch']."
Node:".$row['node']."
"; 41 | echo "

Record entered successfully."; 42 | } 43 | } else { 44 | echo "ERROR: Missing field data. Please try again."; 45 | } 46 | 47 | echo "

"; 48 | 49 | echo ""; 50 | 51 | ?> -------------------------------------------------------------------------------- /head.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | hMailServer pURI-BL 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hMailServer_pURIBL 2 | Personal URIBL for hMailServer 3 | 4 | # Description 5 | Scans spam (messages with spam score above delete threshold) for URIs and inserts them into database. 6 | 7 | Reviews incoming messages for matching URIs and scores them as spam if matching URI found. 8 | 9 | # Instructions 10 | Create database 11 | 12 | ``` 13 | CREATE TABLE IF NOT EXISTS hm_puribluri ( 14 | id int(11) NOT NULL AUTO_INCREMENT, 15 | uri text NOT NULL, 16 | domain varchar(128) NOT NULL, 17 | timestamp datetime NOT NULL, 18 | adds mediumint(9) NOT NULL, 19 | hits mediumint(9) NOT NULL, 20 | active tinyint(1) NOT NULL, 21 | PRIMARY KEY (id), 22 | UNIQUE KEY uri (uri) USING HASH 23 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 24 | COMMIT; 25 | 26 | CREATE TABLE IF NOT EXISTS hm_puribldom ( 27 | id int(11) NOT NULL AUTO_INCREMENT, 28 | domain text NOT NULL, 29 | timestamp datetime NOT NULL, 30 | adds mediumint(9) NOT NULL, 31 | hits mediumint(9) NOT NULL, 32 | shortcircuit tinyint(1) NOT NULL, 33 | PRIMARY KEY (id), 34 | UNIQUE KEY domain (domain) USING HASH 35 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 36 | COMMIT; 37 | ``` 38 | 39 | Copy the contents of Events/EventHandlers.vbs into your EventHandlers.vbs. 40 | 41 | Copy Events/public_suffix_list.vbs into hMailServer Events folder. Alternatively, make your own using PublicSuffixLoad.ps1. public_suffix_list.vbs should be updated monthly as the list changes over time. 42 | 43 | Copy contents of main folder (PHP files + VERSION file) into web server for management tools. Rename config.php.dist to config.php and update the variables. 44 | 45 | # PHP Management 46 | There are two categories: Domains and URIs. 47 | 48 | URIs are full URIs. They can be set to inactive so they won't be used for matching. 49 | 50 | Domains are the highest full domain not including subdomains. They are scraped from the URIs collected. They can be set to short circuit, which means if a match is made with the domain, it will score and stop looking for individual URIs. Deleting a domain will also delete all associated URIs. 51 | 52 | Clicking on URIs or domains, of course, will not launch your browser toward a potential virus. Clicking on a domain will search for matching URIs. Clicking on a URI will bring up its properties for editing. 53 | -------------------------------------------------------------------------------- /edit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | exec($sql); 19 | header("Location: ".$_POST['referrer']); 20 | } 21 | } 22 | if (isset($_POST['delete'])) { 23 | $sql = "DELETE FROM ".$Database['tableuriname']." WHERE id='".$id."';"; 24 | $pdo->exec($sql); 25 | header("Location: ".$_POST['referrer']); 26 | } 27 | 28 | echo "
"; 29 | echo "

"; 30 | echo "ID: ".$id.""; 31 | echo "

"; 32 | 33 | $sql = $pdo->prepare("SELECT * FROM ".$Database['tableuriname']." WHERE id = '".$id."';"); 34 | $sql->execute(); 35 | echo ""; 36 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 37 | echo " 38 | "; 39 | echo " 40 | 41 | 44 | "; 45 | echo " 46 | 47 | 48 | "; 49 | echo " 50 | 51 | 52 | "; 53 | echo " 54 | 55 | 56 | "; 57 | if ($row['active']==0) { 58 | $active=""; 59 | } else { 60 | $active=""; 61 | } 62 | echo " 63 | 64 | 65 | "; 66 | echo " 67 | 68 | 69 | "; 70 | echo " 71 | 72 | 73 | 74 | "; 75 | } 76 | echo "
URI: 42 | 43 |
Adds:".$row['adds']."
Hits:".$row['hits']."
Last Hit:".date("y/n/j G:i:s", strtotime($row['timestamp']))."
Active:".$active."
Delete:
Edit:
"; 77 | 78 | echo "

"; 79 | 80 | echo "
"; 81 | 82 | 83 | ?> -------------------------------------------------------------------------------- /Events/PublicSuffixLoad.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Download and format public_suffix_list.dat for use as RegEx pattern 5 | 6 | .DESCRIPTION 7 | Download and format public_suffix_list.dat for use as RegEx pattern 8 | 9 | .FUNCTIONALITY 10 | * Downloads public_suffix_list.dat 11 | * Converts public_suffix_list.dat to RegEx pattern 12 | * Outputs to vbs file for use with EventHandlers.vbs 13 | * Outputs to powershell file for use with whatever you want 14 | 15 | .NOTES 16 | Run weekly from task scheduler 17 | 18 | .EXAMPLE 19 | 20 | #> 21 | 22 | <# Set script variables #> 23 | $URL = "https://publicsuffix.org/list/public_suffix_list.dat" 24 | $PubSufFile = "$PSScriptRoot\public_suffix_list.dat" 25 | $CondensedDatList = "$PSScriptRoot\public_suffix_list.vbs" 26 | $PSPubSuf = "$PSScriptRoot\public_suffix_list.ps1" 27 | 28 | <# Download latest Public Suffix data #> 29 | <# First, if public_suffix_list.dat exists, get age in hours #> 30 | If (Test-Path $PubSufFile) { 31 | $LastDownloadTime = (Get-Item $PubSufFile).LastWriteTime 32 | $HoursSinceLastDownload = [int](New-Timespan $LastDownloadTime).TotalHours 33 | } 34 | 35 | <# If public_suffix_list.dat doesn't exist or file at least 1 day old, then download #> 36 | # If (($HoursSinceLastDownload -gt 23) -or (-not(Test-Path $PubSufFile))) { 37 | Try { 38 | Start-BitsTransfer -Source $URL -Destination $PubSufFile -ErrorAction Stop 39 | } 40 | Catch { 41 | $Err = $Error[0] 42 | Write-Output "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) : Error downloading Public Suffix List : `n$Err" | Out-File "$PSScriptRoot\PubSufError.log" -Append 43 | Exit 44 | } 45 | # } 46 | 47 | <# Read data file and output list formatted for RegEx (surround each with ^ and $) #> 48 | Get-Content $PubSufFile | Where {((-not([string]::IsNullOrEmpty($_))) -and ($_ -notmatch "^//|^\*|^\!"))} | ForEach { 49 | Write-Output "^$_$" 50 | } | Out-File $CondensedDatList 51 | 52 | <# Convert list to RegEx pattern #> 53 | (Get-Content -Path $CondensedDatList) -Replace '$','|' | Set-Content -NoNewline -Path $CondensedDatList 54 | # (Get-Content -Path $CondensedDatList) -Replace '\.','\.' | Set-Content -NoNewline -Path $CondensedDatList 55 | (Get-Content -Path $CondensedDatList) -Replace '^','Dim PubSufRegEx : PubSufRegEx = "' | Set-Content -NoNewline -Path $CondensedDatList 56 | (Get-Content -Path $CondensedDatList) -Replace '\|$','' | Set-Content -NoNewline -Path $CondensedDatList 57 | (Get-Content -Path $CondensedDatList) -Replace '$','"' | Set-Content -NoNewline -Path $CondensedDatList 58 | 59 | (Get-Content -Path $CondensedDatList -Encoding UTF8) -Replace '^Dim PubSufRegEx : PubSufRegEx','[RegEx]$PubSufRegEx' | Set-Content -NoNewline -Path $PSPubSuf -Encoding UTF8 -------------------------------------------------------------------------------- /login.php: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | 35 | 36 | Log In 37 | 38 | 39 | 43 | 44 | 45 |
46 |

Log In

47 |
" method="post"> 48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 |
63 |
64 | "; 67 | echo "alert('Username/Password Invalid');"; 68 | echo ""; 69 | } 70 | ?> 71 |
72 | 73 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | ".$search."\""; 21 | $search_page = "&search=".$search; 22 | } else { 23 | $search = ""; 24 | $search_ph = ""; 25 | $search_SQL = ""; 26 | $search_res = ""; 27 | $search_page = ""; 28 | } 29 | 30 | if (isset($_GET['clear'])) { 31 | header("Location: index.php"); 32 | } 33 | 34 | if(isset($_POST['updateactive'])){ 35 | if(!empty($_POST['updateactive'])) { 36 | $keys = array_keys($_POST['updateactive']); 37 | for ($i = 0; $i < count($_POST['updateactive']); $i++){ 38 | $sql = " 39 | UPDATE ".$Database['tableuriname']." 40 | SET active='".$_POST['updateactive'][$keys[$i]]."' 41 | WHERE id='".$keys[$i]."'; 42 | "; 43 | $pdo->exec($sql); 44 | } 45 | } 46 | } 47 | 48 | echo " 49 |
50 |
51 |

52 | 53 | 54 | 55 |
56 |
57 |
58 | 59 |
60 |
"; 61 | 62 | $offset = ($page-1) * $no_of_records_per_page; 63 | 64 | $total_pages_sql = $pdo->prepare(" 65 | SELECT Count( * ) AS count 66 | FROM ".$Database['tableuriname']." 67 | ".$search_SQL 68 | ); 69 | $total_pages_sql->execute(); 70 | $total_rows = $total_pages_sql->fetchColumn(); 71 | $total_pages = ceil($total_rows / $no_of_records_per_page); 72 | 73 | $sql = $pdo->prepare(" 74 | SELECT * FROM ".$Database['tableuriname']." 75 | ".$search_SQL." 76 | ORDER BY timestamp DESC 77 | LIMIT ".$offset.", ".$no_of_records_per_page 78 | ); 79 | $sql->execute(); 80 | 81 | if ($total_pages < 2){ 82 | $pagination = ""; 83 | } else { 84 | $pagination = "(Page: ".number_format($page)." of ".number_format($total_pages).")"; 85 | } 86 | 87 | if ($total_rows == 1){$singular = '';} else {$singular= 's';} 88 | if ($total_rows == 0){ 89 | if ($search == ""){ 90 | echo "Please enter a search term"; 91 | } else { 92 | echo "No results ".$search_res; 93 | } 94 | } else { 95 | if ($total_pages == 1){ 96 | echo ""; 97 | } else { 98 | echo " 99 | 100 |
    101 | "; 102 | if($page <= 1){echo "
  • First
  • ";} else {echo "
  • First
  • ";} 103 | if($page <= 1){echo "
  • Prev
  • ";} else {echo "
  • Prev
  • ";} 104 | if($page >= $total_pages){echo "
  • Next
  • ";} else {echo "
  • Next
  • ";} 105 | if($page >= $total_pages){echo "
  • Last
  • ";} else {echo "
  • Last
  • ";} 106 | echo " 107 |
108 |
"; 109 | } 110 | echo " 111 |
112 |
113 | Results: ".number_format($total_rows)." Record".$singular." ".$pagination." 114 |
115 |
"; 116 | 117 | 118 | echo " 119 |
120 |
121 |
122 |
URI
123 |
Last
124 |
Adds
125 |
Hits
126 |
De/Activate
127 |
"; 128 | 129 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 130 | if ($row['active']) {$checked = "checked";} else {$checked = "";} 131 | echo " 132 |
133 | 134 |
".date("y/m/d H:i:s", strtotime($row['timestamp']))."
135 |
".number_format($row['adds'])."
136 |
".number_format($row['hits'])."
137 |
138 |
139 | 140 | 141 | 145 |
146 |
147 |
"; 148 | } 149 | echo " 150 |
151 |
152 | "; // End table 153 | 154 | if ($total_pages == 1){ 155 | echo ""; 156 | } else { 157 | echo " 158 | 159 |
    160 | "; 161 | if($page <= 1){echo "
  • First
  • ";} else {echo "
  • First
  • ";} 162 | if($page <= 1){echo "
  • Prev
  • ";} else {echo "
  • Prev
  • ";} 163 | if($page >= $total_pages){echo "
  • Next
  • ";} else {echo "
  • Next
  • ";} 164 | if($page >= $total_pages){echo "
  • Last
  • ";} else {echo "
  • Last
  • ";} 165 | echo " 166 |
167 |
168 | "; 169 | } 170 | } 171 | 172 | 173 | ?> 174 | 179 | 193 | 194 |
195 | 196 | -------------------------------------------------------------------------------- /domains.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | ".$search."\""; 21 | $search_page = "&search=".$search; 22 | } else { 23 | $search = ""; 24 | $search_ph = ""; 25 | $search_SQL = ""; 26 | $search_res = ""; 27 | $search_page = ""; 28 | } 29 | 30 | if (isset($_GET['clear'])) { 31 | header("Location: domains.php"); 32 | } 33 | 34 | if(isset($_POST['shortcircuit'])){ 35 | if(!empty($_POST['shortcircuit'])) { 36 | $keys = array_keys($_POST['shortcircuit']); 37 | for ($i = 0; $i < count($_POST['shortcircuit']); $i++){ 38 | $sql = " 39 | UPDATE ".$Database['tabledomname']." 40 | SET shortcircuit = '".$_POST['shortcircuit'][$keys[$i]]."' 41 | WHERE id = '".$keys[$i]."'; 42 | "; 43 | $pdo->exec($sql); 44 | } 45 | } 46 | } 47 | 48 | if(isset($_POST['delete'])){ 49 | if(!empty($_POST['delete'])) { 50 | $sql_dom = " 51 | DELETE FROM ".$Database['tabledomname']." 52 | WHERE id = '".explode(",",$_POST['delete'])[0]."'; 53 | "; 54 | $pdo->exec($sql_dom); 55 | $sql_uri = " 56 | DELETE FROM ".$Database['tableuriname']." 57 | WHERE domain = '".explode(",",$_POST['delete'])[1]."'; 58 | "; 59 | $pdo->exec($sql_uri); 60 | } 61 | } 62 | 63 | echo " 64 |
65 |
66 |

67 | 68 | 69 | 70 |
71 |
72 |
73 | 74 |
75 |
"; 76 | 77 | $offset = ($page-1) * $no_of_records_per_page; 78 | 79 | $total_pages_sql = $pdo->prepare(" 80 | SELECT Count( * ) AS count 81 | FROM ".$Database['tabledomname']." 82 | WHERE id > 0".$search_SQL 83 | ); 84 | $total_pages_sql->execute(); 85 | $total_rows = $total_pages_sql->fetchColumn(); 86 | $total_pages = ceil($total_rows / $no_of_records_per_page); 87 | 88 | $sql = $pdo->prepare(" 89 | SELECT * FROM ".$Database['tabledomname']." 90 | WHERE id > 0".$search_SQL." 91 | ORDER BY timestamp DESC 92 | LIMIT ".$offset.", ".$no_of_records_per_page."; 93 | "); 94 | $sql->execute(); 95 | 96 | if ($total_pages < 2){ 97 | $pagination = ""; 98 | } else { 99 | $pagination = "(Page: ".number_format($page)." of ".number_format($total_pages).")"; 100 | } 101 | 102 | if ($total_rows == 1){$singular = '';} else {$singular= 's';} 103 | if ($total_rows == 0){ 104 | if ($search == ""){ 105 | echo "Please enter a search term"; 106 | } else { 107 | echo "No results ".$search_res.$shortcircuit_res; 108 | } 109 | } else { 110 | if ($total_pages == 1){ 111 | echo ""; 112 | } else { 113 | echo " 114 | 115 |
    116 | "; 117 | if($page <= 1){echo "
  • First
  • ";} else {echo "
  • First
  • ";} 118 | if($page <= 1){echo "
  • Prev
  • ";} else {echo "
  • Prev
  • ";} 119 | if($page >= $total_pages){echo "
  • Next
  • ";} else {echo "
  • Next
  • ";} 120 | if($page >= $total_pages){echo "
  • Last
  • ";} else {echo "
  • Last
  • ";} 121 | echo " 122 |
123 |
"; 124 | } 125 | echo " 126 |
127 |
128 | Results: ".number_format($total_rows)." Record".$singular." ".$pagination." 129 |
130 |
131 | 132 |
133 |
134 |
135 |
Domain
136 |
Last
137 |
Adds
138 |
Hits
139 |
Short Circuit
140 |
Delete
141 |
"; 142 | 143 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 144 | if ($row['shortcircuit']) {$checked = "checked";} else {$checked = "";} 145 | echo " 146 |
147 | 148 |
".date("y/m/d H:i:s", strtotime($row['timestamp']))."
149 |
".number_format($row['adds'])."
150 |
".number_format($row['hits'])."
151 |
152 |
153 | 154 | 155 | 159 |
160 |
161 |
162 | 163 |
164 |
"; 165 | } 166 | echo " 167 |
168 |
169 | "; // End table 170 | 171 | if ($total_pages == 1){ 172 | echo ""; 173 | } else { 174 | echo " 175 | 176 |
    177 | "; 178 | if($page <= 1){echo "
  • First
  • ";} else {echo "
  • First
  • ";} 179 | if($page <= 1){echo "
  • Prev
  • ";} else {echo "
  • Prev
  • ";} 180 | if($page >= $total_pages){echo "
  • Next
  • ";} else {echo "
  • Next
  • ";} 181 | if($page >= $total_pages){echo "
  • Last
  • ";} else {echo "
  • Last
  • ";} 182 | echo " 183 |
184 |
"; 185 | } 186 | } 187 | 188 | ?> 189 | 194 | 203 | 204 |
205 | 206 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fefefe; 3 | font-family: "Roboto"; 4 | font-size: 12pt; 5 | } 6 | 7 | .header { 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | color: #000; 13 | background: #fefefe; 14 | z-index: 1; 15 | overflow: hidden; 16 | text-align:center; 17 | } 18 | 19 | .header h1 { 20 | font-size:25px; 21 | font-weight:normal; 22 | margin:0 auto; 23 | } 24 | 25 | .header h2 { 26 | font-size:15px; 27 | font-weight:normal; 28 | margin:0 auto; 29 | } 30 | 31 | .wrapper { 32 | max-width: 920px; 33 | position: relative; 34 | margin: 30px auto 30px auto; 35 | padding-top: 0px; 36 | } 37 | 38 | .clear { 39 | clear: both; 40 | } 41 | 42 | .banner { 43 | width: 100%; 44 | } 45 | 46 | .headlinks { 47 | max-width: 720px; 48 | min-width: 300px; 49 | position:relative; 50 | margin: 0px auto; 51 | } 52 | 53 | .headlinks a:link, a:active, a:visited { 54 | color: red; 55 | text-decoration: underline; 56 | } 57 | 58 | .headlinks a:hover { 59 | color: red; 60 | text-decoration: none; 61 | } 62 | 63 | .section { 64 | padding: 5px 0 15px 0; 65 | margin: 0; 66 | } 67 | 68 | .section a:link, a:visited { 69 | color: black; 70 | text-decoration: none; 71 | } 72 | 73 | .section a:hover, a:active { 74 | color: red; 75 | text-decoration: underline; 76 | } 77 | 78 | .section h2 { 79 | font-size:16px; 80 | font-weight:bold; 81 | text-align:left; 82 | } 83 | 84 | .section h3 { 85 | font-size:16px; 86 | font-weight:bold; 87 | } 88 | 89 | .secleft { 90 | float: left; 91 | width: 49%; 92 | padding-right: 3px; 93 | } 94 | 95 | .secright { 96 | float: right; 97 | width: 49%; 98 | padding-left: 3px; 99 | } 100 | 101 | table.section { 102 | border-collapse: collapse; 103 | border: 1px solid black; 104 | border-spacing: 10px; 105 | width: 100%; 106 | font-size: 10pt; 107 | } 108 | 109 | table.section tr:nth-child(even) { 110 | background-color: #F8F8F8; 111 | padding: 4px; 112 | } 113 | 114 | table.section th, table.section td { 115 | border: 1px solid black; 116 | padding: 4px; 117 | word-wrap: break-word; 118 | } 119 | 120 | .footer { 121 | width: 100%; 122 | text-align: center; 123 | font-size: 8pt; 124 | } 125 | 126 | .nav { 127 | font-size: 0.8em; 128 | } 129 | 130 | .nav a:link, a:active, a:visited { 131 | /* color: red; */ 132 | text-decoration: underline; 133 | } 134 | 135 | .nav a:hover { 136 | /* color: red; */ 137 | text-decoration: none; 138 | } 139 | 140 | ul { 141 | list-style-type: none; 142 | padding: 0; 143 | } 144 | 145 | li { 146 | padding: 0 3px 0 0; 147 | display: inline; 148 | } 149 | 150 | 151 | 152 | /* ### NEW TABLE CSS ### */ 153 | .div-table { 154 | display: table; 155 | width: 100%; 156 | font-size: 0.8em; 157 | border-left: 1px solid #ccc; 158 | border-bottom: 1px solid #ccc; 159 | } 160 | 161 | .div-table-row-header { 162 | display: table-row; 163 | font-weight: bold; 164 | text-align: center; 165 | } 166 | 167 | .div-table-row { 168 | display: table-row; 169 | } 170 | 171 | .div-table-row:nth-of-type(even) { 172 | background: #eee; 173 | border: 1px solid #ccc; 174 | } 175 | 176 | .center { 177 | text-align: center; 178 | } 179 | 180 | .div-table-col { 181 | display: table-cell; 182 | padding: 3px; 183 | border-right: 1px solid #ccc; 184 | border-top: 1px solid #ccc; 185 | } 186 | 187 | .uriwidth { 188 | width: 500px; 189 | } 190 | 191 | .truncate{ 192 | display: block; 193 | max-width: 500px; 194 | white-space: nowrap; 195 | overflow: hidden; 196 | text-overflow: ellipsis; 197 | } 198 | 199 | /* ### TOGGLE SWITCH ### */ 200 | .onoffswitch { 201 | position: relative; 202 | width: 40px; 203 | -webkit-user-select: none; 204 | -moz-user-select: none; 205 | -ms-user-select: none; 206 | top: 0px; 207 | left: 0px; 208 | margin: auto; 209 | } 210 | 211 | .onoffswitch-checkbox { 212 | display: none; 213 | } 214 | 215 | .onoffswitch-label { 216 | display: block; 217 | overflow: hidden; 218 | cursor: pointer; 219 | border: 1px solid #999999; 220 | border-radius: 20px; 221 | } 222 | 223 | .onoffswitch-inner { 224 | width: 200%; 225 | margin-left: -100%; 226 | -moz-transition: margin 0.2s ease-in 0s; 227 | -webkit-transition: margin 0.2s ease-in 0s; 228 | -o-transition: margin 0.2s ease-in 0s; 229 | transition: margin 0.2s ease-in 0s; 230 | } 231 | 232 | .onoffswitch-inner:before, 233 | .onoffswitch-inner:after { 234 | float: left; 235 | width: 50%; 236 | height: 3px; 237 | padding: 0; 238 | line-height: 2px; 239 | font-size: 4px; 240 | color: white; 241 | font-family: Trebuchet, Arial, sans-serif; 242 | font-weight: bold; 243 | -moz-box-sizing: border-box; 244 | -webkit-box-sizing: border-box; 245 | box-sizing: border-box; 246 | } 247 | 248 | .onoffswitch-inner:before { 249 | content: ""; 250 | padding-left: 10px; 251 | background-color: #ff5154; 252 | color: #FFFFFF; 253 | } 254 | 255 | .onoffswitch-inner:after { 256 | content: ""; 257 | padding-right: 10px; 258 | background-color: #EEEEEE; 259 | color: #999999; 260 | text-align: right; 261 | } 262 | 263 | .onoffswitch-switch { 264 | height: 10px; 265 | width: 10px; 266 | margin: -5px; 267 | background: #FFFFFF; 268 | border: 2px solid #999999; 269 | border-radius: 20px; 270 | position: absolute; 271 | top: 0; 272 | bottom: 0; 273 | right: 31px; 274 | -moz-transition: all 0.2s ease-in 0s; 275 | -webkit-transition: all 0.2s ease-in 0s; 276 | -o-transition: all 0.2s ease-in 0s; 277 | transition: all 0.2s ease-in 0s; 278 | } 279 | 280 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { 281 | margin-left: 0; 282 | } 283 | 284 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { 285 | right: 0px; 286 | } 287 | 288 | .switchleft { 289 | float: left; 290 | width:80px; 291 | } 292 | 293 | .switchright { 294 | margin-left: 80px; 295 | text-align: left; 296 | } 297 | 298 | @media only screen and (max-width: 790px) { 299 | 300 | .onoffswitch { 301 | margin: 5px 0; 302 | } 303 | 304 | .truncate{ 305 | display: none; 306 | max-width: calc(100% - 90px); 307 | /* white-space: normal; */ 308 | overflow: auto; 309 | } 310 | 311 | .secleft { 312 | float: none; 313 | width: 100%; 314 | padding: 0 0 10px 0; 315 | text-align: left; 316 | } 317 | .secright { 318 | float: none ; 319 | width: 100% ; 320 | } 321 | 322 | /* ### NEW TABLE CSS ### */ 323 | .div-table, .div-table-row-header, .div-table-row, .div-table-col { 324 | display: block; 325 | } 326 | 327 | .div-table { 328 | border: none; 329 | } 330 | 331 | /* Hide table headers (but not display: none;, for accessibility) */ 332 | .div-table-row-header { 333 | position: absolute; 334 | top: -9999px; 335 | left: -9999px; 336 | } 337 | 338 | .div-table-row { 339 | border: none; 340 | border-left: 1px solid #ccc; 341 | border-right: 1px solid #ccc; 342 | } 343 | 344 | .div-table-col { 345 | /* Behave like a "row" */ 346 | border: none; 347 | border-bottom: 1px solid #e6e6e6; 348 | position: relative; 349 | padding-left: 90px; 350 | text-align: left; 351 | } 352 | 353 | .div-table-col:before { 354 | /* Now like a table header */ 355 | border: none; 356 | position: absolute; 357 | top: 3px; 358 | left: 6px; 359 | padding-right: 10px; 360 | /* Label the data */ 361 | content: attr(data-column); 362 | color: #000; 363 | font-weight: normal; 364 | /* font-weight: bold; */ 365 | } 366 | 367 | .whitespace { 368 | white-space: pre; /* CSS 2.0 */ 369 | white-space: pre-wrap; /* CSS 2.1 */ 370 | white-space: pre-line; /* CSS 3.0 */ 371 | white-space: -pre-wrap; /* Opera 4-6 */ 372 | white-space: -o-pre-wrap; /* Opera 7 */ 373 | white-space: -moz-pre-wrap; /* Mozilla */ 374 | white-space: -hp-pre-wrap; /* HP Printers */ 375 | word-wrap: break-word; /* IE 5+ */ 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /Events/EventHandlers.vbs: -------------------------------------------------------------------------------- 1 | Option Explicit 2 | 3 | Private Const DeleteThreshold = 7 'hMailServer spam delete threshold 4 | Private Const hMSPASSWORD = "SuperSecrecthMailServerPassword" 'hMailServer COM password (Administrator password) 5 | Private Const hMSdbPW = "SuperSecretDatabasePassword" 'hMailServer MySQL database user password 6 | Private Const pURIBLURITable = "hm_puribluri" 'pURIBL URI table name 7 | Private Const pURIBLDomTable = "hm_puribldom" 'pURIBL domain table name 8 | Private Const PublicSuffix = "C:\Program Files (x86)\hMailServer\Events\public_suffix_list.vbs" 'Path to public_suffix_list.vbs 9 | Private pURIBLDict : Set pURIBLDict = CreateObject("Scripting.Dictionary") 10 | 11 | Function Lookup(strRegEx, strMatch) : Lookup = False 12 | With CreateObject("VBScript.RegExp") 13 | .Pattern = strRegEx 14 | .Global = False 15 | .MultiLine = True 16 | .IgnoreCase = True 17 | If .Test(strMatch) Then Lookup = True 18 | End With 19 | End Function 20 | 21 | Function oLookup(strRegEx, strMatch, bGlobal) 22 | If strRegEx = "" Then strRegEx = StrReverse(strMatch) 23 | With CreateObject("VBScript.RegExp") 24 | .Pattern = strRegEx 25 | .Global = bGlobal 26 | .MultiLine = True 27 | .IgnoreCase = True 28 | Set oLookup = .Execute(strMatch) 29 | End With 30 | End Function 31 | 32 | Function GetDatabaseObject() 33 | Dim oApp : Set oApp = CreateObject("hMailServer.Application") 34 | Call oApp.Authenticate("Administrator", hMSPASSWORD) 35 | Set GetDatabaseObject = oApp.Database 36 | End Function 37 | 38 | Function Include(sInstFile) 39 | Dim f, s, oFSO 40 | Set oFSO = CreateObject("Scripting.FileSystemObject") 41 | On Error Resume Next 42 | If oFSO.FileExists(sInstFile) Then 43 | Set f = oFSO.OpenTextFile(sInstFile) 44 | s = f.ReadAll 45 | f.Close 46 | ExecuteGlobal s 47 | End If 48 | On Error Goto 0 49 | Set f = Nothing 50 | Set oFSO = Nothing 51 | End Function 52 | 53 | Sub BlackList(oMessage, strMatch, iScore) 54 | Dim i, Done : Done = False 55 | If CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score")) > 0 Then 56 | i = CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score")) 57 | Else 58 | oMessage.HeaderValue("X-hMailServer-Spam") = "YES" 59 | i = 0 60 | End If 61 | oMessage.HeaderValue("X-hMailServer-Blacklist") = "YES" 62 | oMessage.HeaderValue("X-hMailServer-Reason-0") = "BlackListed - (Score: " & iScore & ")" 63 | oMessage.HeaderValue("X-hMailServer-Reason-Score") = iScore + i 64 | i = 1 65 | Do Until Done 66 | If (oMessage.HeaderValue("X-hMailServer-BlackList-" & i) = "") Then 67 | oMessage.HeaderValue("X-hMailServer-BlackList-" & i) = strMatch 68 | Exit Do 69 | Else 70 | i = i + 1 71 | End If 72 | Loop 73 | oMessage.Save 74 | End Sub 75 | 76 | Function GetMainDomain(strDomain) 77 | Dim strRegEx, Match, Matches 78 | Dim TestDomain, DomainParts, a, i, PubSuffMatch 79 | Include(PublicSuffix) 80 | 81 | DomainParts = Split(strDomain,".") 82 | a = UBound(DomainParts) 83 | If a > 1 Then 84 | TestDomain = DomainParts(1) 85 | For i = 2 to a 86 | TestDomain = TestDomain & "." & DomainParts(i) 87 | Next 88 | ElseIf a = 1 Then 89 | TestDomain = DomainParts(1) 90 | Else 91 | Exit Function 92 | End If 93 | 94 | Set Matches = oLookup(PubSufRegEx, TestDomain, False) 95 | For Each Match In Matches 96 | PubSuffMatch = True 97 | Next 98 | 99 | If PubSuffMatch Then 100 | GetMainDomain = DomainParts(0) & "." & TestDomain 101 | Else 102 | GetMainDomain = GetMainDomain(TestDomain) 103 | End If 104 | End Function 105 | 106 | Function ExcludeHead(strStr) 107 | Dim Head 108 | If InStr(strStr, "") Then Head = "" Else If InStr(strStr, "") Then Head = "" Else Head = "" 109 | If InStr(strStr, Head) > 0 Then 110 | ExcludeHead = Right(strStr, ((Len(strStr)) - (InStr(strStr, Head) + 7))) 111 | Else 112 | ExcludeHead = strStr 113 | End If 114 | End Function 115 | 116 | '### pURIBL MySQL Table Structure ###' 117 | ' 118 | ' CREATE TABLE IF NOT EXISTS hm_puribluri ( 119 | ' id int(11) NOT NULL AUTO_INCREMENT, 120 | ' strid varchar(32) NOT NULL, 121 | ' uri text NOT NULL, 122 | ' domain varchar(128) NOT NULL, 123 | ' timestamp datetime NOT NULL, 124 | ' adds mediumint(9) NOT NULL, 125 | ' hits mediumint(9) NOT NULL, 126 | ' active tinyint(1) NOT NULL, 127 | ' PRIMARY KEY (id), 128 | ' UNIQUE KEY uri (uri) USING HASH 129 | ' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 130 | ' COMMIT; 131 | 132 | ' CREATE TABLE IF NOT EXISTS hm_puribldom ( 133 | ' id int(11) NOT NULL AUTO_INCREMENT, 134 | ' strid varchar(32) NOT NULL, 135 | ' domain text NOT NULL, 136 | ' timestamp datetime NOT NULL, 137 | ' adds mediumint(9) NOT NULL, 138 | ' hits mediumint(9) NOT NULL, 139 | ' shortcircuit tinyint(1) NOT NULL, 140 | ' PRIMARY KEY (id), 141 | ' UNIQUE KEY uri (domain) USING HASH 142 | ' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 143 | ' COMMIT; 144 | 145 | Function pURIBLRegEx(pURIBLDict, pType) : pURIBLRegEx = "" 146 | Dim strData, pID, pRecord, pURL, pSQL 147 | Dim oRecord, oDB : Set oDB = CreateObject("ADODB.Connection") 148 | oDB.Open "Driver={MariaDB ODBC 3.1 Driver}; Server=localhost; Database=hmailserver; User=hmailserver; Password=" & hMSdbPW & ";" 149 | If oDB.State <> 1 Then 150 | EventLog.Write( "pURIBLRegEx - ERROR: Could not connect to database" ) 151 | pURIBLRegEx = "VOID" 152 | Exit Function 153 | End If 154 | 155 | pURIBLDict.RemoveAll 156 | 157 | If pType = "DOM" Then 158 | pSQL = "SELECT * FROM " & pURIBLDomTable & " WHERE shortcircuit = 1;" 159 | pRecord = "domain" 160 | Else 161 | pSQL = "SELECT * FROM " & pURIBLURITable & " WHERE active = 1;" 162 | pRecord = "uri" 163 | End If 164 | 165 | Set oRecord = oDB.Execute(pSQL) 166 | Do Until oRecord.EOF 167 | pID = CStr(Trim(oRecord("id"))) 168 | pURL = CStr(Trim(oRecord(pRecord))) 169 | If (pURL <> "") Then 170 | strData = strData & pURL & "|" 171 | pURIBLDict.Add pID, pURL 172 | End If 173 | oRecord.MoveNext 174 | Loop 175 | 176 | If (Trim(strData) <> "") Then 177 | pURIBLRegEx = Left(strData,Len(strData)-1) 178 | Else 179 | EventLog.Write("ERROR: pURIBLEDomRegEx: Database returned no records") 180 | pURIBLRegEx = "VOID" 181 | End If 182 | 183 | Set oRecord = Nothing 184 | oDB.Close 185 | Set oDB = Nothing 186 | End Function 187 | 188 | Function pURIBLStat(pURIBLDict, oMatchValue, pType) 189 | Dim strSQL, oDB, objKey, pTable 190 | Set oDB = CreateObject("ADODB.Connection") 191 | oDB.Open "Driver={MariaDB ODBC 3.1 Driver}; Server=localhost; Database=hmailserver; User=hmailserver; Password=" & hMSdbPW & ";" 192 | If oDB.State <> 1 Then 193 | EventLog.Write( "pURIBLStat - ERROR: Could not connect to database" ) 194 | pURIBLStat = "VOID" 195 | Exit Function 196 | End If 197 | 198 | If pType = "DOM" Then pTable = pURIBLDomTable Else pTable = pURIBLURITable 199 | For Each objKey In pURIBLDict 200 | If Lookup(CStr(pURIBLDict(objKey)), oMatchValue) Then 201 | strSQL = "UPDATE " & pTable & " SET timestamp = NOW(), hits = (hits + 1) WHERE id = '" & CStr(objKey) & "';" 202 | Call oDB.Execute(strSQL) 203 | Exit For 204 | End If 205 | Next 206 | Set oDB = Nothing 207 | End Function 208 | 209 | Sub OnAcceptMessage(oClient, oMessage) 210 | 211 | REM - Exclude authenticated users test 212 | If (oClient.Username <> "") Then Exit Sub 213 | 214 | Dim strRegEx, Match, Matches 215 | Dim oMatch, oMatchCollection 216 | Dim Done 217 | 218 | REM - Blacklist on pURIBL 219 | Dim pURIBLBlacklistScore : pURIBLBlacklistScore = 4 'Blacklist Score 220 | Done = False 221 | Do Until Done 222 | 223 | REM - Blacklist on pURIBL Domain match - plain text body 224 | strRegEx = pURIBLRegEx(pURIBLDict, "DOM") 225 | If strRegEx <> "VOID" Then 226 | Set oMatchCollection = oLookup(strRegEx, oMessage.Body, False) 227 | For Each oMatch In oMatchCollection 228 | Call pURIBLStat(pURIBLDict, oMatch.Value, "DOM") 229 | Call BlackList(oMessage, "pURIBL Dom = '" & oMatch.Value & "'", pURIBLBlacklistScore) 230 | Exit Do 231 | Next 232 | End If 233 | 234 | REM - Blacklist on pURIBL Domain match - HTML body 235 | strRegEx = pURIBLRegEx(pURIBLDict, "DOM") 236 | If strRegEx <> "VOID" Then 237 | Set oMatchCollection = oLookup(strRegEx, ExcludeHead(oMessage.HTMLBody), False) 238 | For Each oMatch In oMatchCollection 239 | Call pURIBLStat(pURIBLDict, oMatch.Value, "DOM") 240 | Call BlackList(oMessage, "pURIBL Dom = '" & oMatch.Value & "'", pURIBLBlacklistScore) 241 | Exit Do 242 | Next 243 | End If 244 | 245 | REM - Blacklist on pURIBL URI match - plain text body 246 | strRegEx = pURIBLRegEx(pURIBLDict, "URI") 247 | If strRegEx <> "VOID" Then 248 | Set oMatchCollection = oLookup(strRegEx, oMessage.Body, False) 249 | For Each oMatch In oMatchCollection 250 | Call pURIBLStat(pURIBLDict, oMatch.Value, "URI") 251 | Call BlackList(oMessage, "pURIBL URI = '" & oMatch.Value & "'", pURIBLBlacklistScore) 252 | Exit Do 253 | Next 254 | End If 255 | 256 | REM - Blacklist on pURIBL URI match - HTML body 257 | strRegEx = pURIBLRegEx(pURIBLDict, "URI") 258 | If strRegEx <> "VOID" Then 259 | Set oMatchCollection = oLookup(strRegEx, ExcludeHead(oMessage.HTMLBody), False) 260 | For Each oMatch In oMatchCollection 261 | Call pURIBLStat(pURIBLDict, oMatch.Value, "URI") 262 | Call BlackList(oMessage, "pURIBL URI = '" & oMatch.Value & "'", pURIBLBlacklistScore) 263 | Exit Do 264 | Next 265 | End If 266 | 267 | Done = True 268 | Loop 269 | 270 | End Sub 271 | 272 | Sub OnDeliveryStart(oMessage) 273 | 274 | REM - Populate pURIBL 275 | If oMessage.HeaderValue("X-hMailServer-Reason-Score") <> "" Then 276 | If CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score")) >= DeleteThreshold Then 277 | 278 | Dim strSQL, strSQLD, oDB : Set oDB = GetDatabaseObject 279 | Dim strRegEx, Match, Matches 280 | Dim strRegExD, MatchD, MatchesD 281 | 282 | strRegEx = "(\b((https?(:\/\/|%3A%2F%2F))((([a-zA-Z0-9-]+)\.)+[a-zA-Z0-9-]+)(:\d+)?((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)\b)" 283 | Set Matches = oLookup(strRegEx, ExcludeHead(oMessage.Body), True) 284 | For Each Match In Matches 285 | strRegExD = "(?:^https?)(?::\/\/|%3A%2F%2F)(?:[^@\/\n]+@)?([^:\/%?\n]+)" 286 | Set MatchesD = oLookup(strRegExD, Match.SubMatches(0), True) 287 | For Each MatchD In MatchesD 288 | strSQL = "INSERT INTO " & pURIBLURITable & " (uri,domain,timestamp,adds,hits,active) VALUES ('" & Match.SubMatches(0) & "','" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,0,1) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 289 | Call oDB.ExecuteSQL(strSQL) 290 | strSQLD = "INSERT INTO " & pURIBLDomTable & " (domain,timestamp,adds,hits,shortcircuit) VALUES ('" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,0,0) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 291 | Call oDB.ExecuteSQL(strSQLD) 292 | Next 293 | Next 294 | 295 | Set Matches = oLookup(strRegEx, ExcludeHead(oMessage.HTMLBody), True) 296 | For Each Match In Matches 297 | strRegExD = "(?:^https?)(?::\/\/|%3A%2F%2F)(?:[^@\/\n]+@)?([^:\/%?\n]+)" 298 | Set MatchesD = oLookup(strRegExD, Match.SubMatches(0), True) 299 | For Each MatchD In MatchesD 300 | strSQL = "INSERT INTO " & pURIBLURITable & " (uri,strid,domain,timestamp,adds,active) VALUES ('" & Match.SubMatches(0) & "',MD5('" & Match.SubMatches(0) & "'),'" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,1) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 301 | Call oDB.ExecuteSQL(strSQL) 302 | strSQLD = "INSERT INTO " & pURIBLDomTable & " (domain,strid,timestamp,adds,hits,shortcircuit) VALUES ('" & GetMainDomain(MatchD.SubMatches(0)) & "',MD5('" & GetMainDomain(MatchD.SubMatches(0)) & "'),NOW(),1,0,0) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 303 | Call oDB.ExecuteSQL(strSQLD) 304 | Next 305 | Next 306 | 307 | End If 308 | End If 309 | 310 | End Sub -------------------------------------------------------------------------------- /Events/pURIBL-Offline.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | pURI-BL (Offline) 5 | 6 | .DESCRIPTION 7 | Feeds messages for pURI-BL inspection 8 | 9 | .FUNCTIONALITY 10 | Inspects HAM messages for blacklisted URIs, scores them as spam and moves to spam folder. 11 | 12 | Inspects SPAM messages for URIs to add to blacklist. 13 | 14 | .PARAMETER 15 | 16 | 17 | .NOTES 18 | Add "--allow-tell" argument to your SPAMD service to allow SPAMC to report SPAM. 19 | 20 | Run daily or hourly from task scheduler 21 | 22 | .EXAMPLE 23 | 24 | 25 | #> 26 | 27 | <### SCRIPT VARIABLES ###> 28 | $Simulate = $True # FOR TESTING - set to TRUE to run and report results without modifying database, moving messages or feeding SpamC 29 | $MoveToJunk = $True # If hit on pURI-BL URI during inspection of HAM, move the message to the junk folder 30 | $HamFolders = "" # Names of HAM folders to search messages for blacklisted URIs - uses regex (defaults to "inbox") 31 | $SpamFolders = "" # Potential/actual names of SPAM folders - uses regex - (defaults to "spam|junk") 32 | $SearchSubFolders = $False # True will search subfolders for messages to process 33 | $SearchDays = 1 # Number of days worth of messages to process 34 | $SkipAccounts = "" # User accounts to skip - uses regex (disable with "" or $NULL) 35 | $SkipDomains = "" # Domains to skip - uses regex (disable with "" or $NULL) 36 | $PubSufListLocation = "C:\scripts\puribl\public_suffix_list.ps1" # Location of public_suffix_list.ps1 37 | 38 | <### HMAILSERVER VARIABLES ###> 39 | $hMSAdminPass = "secretpassword" # hMailServer Admin password 40 | $hMSDeleteThreshold = 7 # hMailServer spam delete threshold (used to determine whether to inspect spammy messages for URIs) 41 | 42 | <### SPAMASSASSIN VARIABLES ###> 43 | $FeedSpamC = $False # If hit on pURI-BL URI during inspection of HAM, TRUE will feed message to SpamC as SPAM for bayes learning 44 | $SARuleCF = $False # If true, will create or update custom rule config "pURIBL.cf" in \ect\spamassassin folder 45 | $SADir = "C:\SpamAssassin" # SpamAssassin Install Directory (required to run SpamC and/or create custom rule file) 46 | 47 | <### LOGGING VARIABLES ###> 48 | $VerboseConsole = $True # If true, will output debug to console 49 | $VerboseFile = $True # If true, will output debug to file 50 | 51 | <### MySQL VARIABLES ###> 52 | $pURIBLDomTable = "hm_puribldom" # Name of "domains" table 53 | $pURIBLURITable = "hm_puribluri" # Name of "URI" table 54 | $SQLAdminUserName = "hmailserver" 55 | $SQLAdminPassword = "supersecretpassword" 56 | $SQLDatabase = "hmailserver" 57 | $SQLHost = "localhost" 58 | $SQLPort = 3306 59 | $SQLSSL = "none" 60 | $SQLConnectTimeout = 300 61 | $SQLCommandTimeOut = 9000000 # Leave high if read errors 62 | 63 | <### EMAIL VARIABLES ###> 64 | $EmailFrom = "notify@mydomain.tld" 65 | $EmailTo = "admin@mydomain.tld" 66 | $Subject = "pURI-BL Routine Report" 67 | $SMTPServer = "mail.mydomain.tld" 68 | $SMTPAuthUser = "notify@mydomain.tld" 69 | $SMTPAuthPass = "supersecretpassword" 70 | $SMTPPort = 587 71 | $UseSSL = $True # If true, will use tls connection to send email 72 | $UseHTML = $True # If true, will format and send email body as html (with color!) 73 | $AttachDebugLog = $True # If true, will attach debug log to email report - must also select $VerboseFile 74 | $MaxAttachmentSize = 1 # Max attachment size in MB (will not attach log if greater than $MaxAttachmentSize) 75 | 76 | <### LOAD SUPPORTING FILES ###> 77 | Try { 78 | .($PubSufListLocation) 79 | } 80 | Catch { 81 | Write-Output "$(Get-Date -f G) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$($Error[0])" | Out-File "$PSScriptRoot\PSError.log" -Append 82 | } 83 | 84 | <### FUNCTIONS ###> 85 | Function Debug ($DebugOutput) { 86 | If ($VerboseFile) {Write-Output "$(Get-Date -f G) : $DebugOutput" | Out-File $DebugLog -Encoding ASCII -Append} 87 | If ($VerboseConsole) {Write-Host "$(Get-Date -f G) : $DebugOutput"} 88 | } 89 | 90 | Function Email ($EmailOutput) { 91 | If ($UseHTML){ 92 | If ($EmailOutput -match "\[OK\]") {$EmailOutput = $EmailOutput -Replace "\[OK\]","[OK]"} 93 | If ($EmailOutput -match "\[INFO\]") {$EmailOutput = $EmailOutput -Replace "\[INFO\]","[INFO]"} 94 | If ($EmailOutput -match "\[ERROR\]") {$EmailOutput = $EmailOutput -Replace "\[ERROR\]","[ERROR]"} 95 | If ($EmailOutput -match "^\s$") {$EmailOutput = $EmailOutput -Replace "\s"," "} 96 | Write-Output "$EmailOutput" | Out-File $EmailBody -Encoding ASCII -Append 97 | } Else { 98 | Write-Output $EmailOutput | Out-File $EmailBody -Encoding ASCII -Append 99 | } 100 | } 101 | 102 | Function EmailResults { 103 | Try { 104 | $Body = (Get-Content -Path $EmailBody | Out-String ) 105 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Attachment = New-Object System.Net.Mail.Attachment $DebugLog} 106 | $Message = New-Object System.Net.Mail.Mailmessage $EmailFrom, $EmailTo, $Subject, $Body 107 | $Message.IsBodyHTML = $UseHTML 108 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Message.Attachments.Add($DebugLog)} 109 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort 110 | $SMTP.EnableSsl = $UseSSL 111 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass); 112 | $SMTP.Send($Message) 113 | } 114 | Catch { 115 | Debug "Email ERROR : $($Error[0])" 116 | } 117 | } 118 | 119 | Function ElapsedTime ($EndTime) { 120 | $TimeSpan = New-Timespan $EndTime 121 | If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "} 122 | If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "} 123 | If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"} 124 | If (($TimeSpan).TotalSeconds -lt 1) { 125 | $Return = "less than 1 second" 126 | } Else { 127 | $Return = "$Hours$Minutes$Seconds" 128 | } 129 | Return $Return 130 | } 131 | 132 | Function Plural ($Integer) { 133 | If ($Integer -eq 1) {$S = ""} Else {$S = "s"} 134 | Return $S 135 | } 136 | 137 | Function ValidatePath ($Path) { 138 | If (-not(Test-Path $Path)) { 139 | Debug "[ERROR] Path $Path does not exist : Quitting script" 140 | Email "[ERROR] Path $Path does not exist : Quitting script" 141 | EmailResults 142 | Exit 143 | } 144 | } 145 | 146 | Function MySQLQuery($Query) { 147 | $Today = (Get-Date).ToString("yyyyMMdd") 148 | $DBErrorLog = "$PSScriptRoot\DBError-$Today.log" 149 | $ConnectionString = "server=" + $SQLHost + ";port=" + $SQLPort + ";uid=" + $SQLAdminUserName + ";pwd=" + $SQLAdminPassword + ";database=" + $SQLDatabase + ";SslMode=" + $SQLSSL + ";Default Command Timeout=" + $SQLCommandTimeOut + ";Connect Timeout=" + $SQLConnectTimeout + ";" 150 | Try { 151 | [void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data") 152 | $Connection = New-Object MySql.Data.MySqlClient.MySqlConnection 153 | $Connection.ConnectionString = $ConnectionString 154 | $Connection.Open() 155 | $Command = New-Object MySql.Data.MySqlClient.MySqlCommand($Query, $Connection) 156 | $DataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($Command) 157 | $DataSet = New-Object System.Data.DataSet 158 | $RecordCount = $dataAdapter.Fill($dataSet, "data") 159 | $DataSet.Tables[0] 160 | } 161 | Catch { 162 | Write-Output "$(Get-Date -f G) : ERROR : Unable to run query : $Query" | out-file $DBErrorLog -append 163 | Write-Output "$(Get-Date -f G) : ERROR : $($Error[0])" | out-file $DBErrorLog -append 164 | } 165 | Finally { 166 | $Connection.Close() 167 | } 168 | } 169 | 170 | Function CreateTableStructure { 171 | $SQLURI = " 172 | CREATE TABLE IF NOT EXISTS $pURIBLURITable ( 173 | id int(11) NOT NULL AUTO_INCREMENT, 174 | uri text NOT NULL, 175 | strid varchar(32) NOT NULL, 176 | domain varchar(128) NOT NULL, 177 | timestamp datetime NOT NULL, 178 | adds mediumint(9) NOT NULL, 179 | hits mediumint(9) NOT NULL, 180 | active tinyint(1) NOT NULL, 181 | PRIMARY KEY (id), 182 | UNIQUE KEY uri (uri) USING HASH 183 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 184 | COMMIT; 185 | " 186 | MySQLQuery $SQLURI 187 | 188 | $SQLDom = " 189 | CREATE TABLE IF NOT EXISTS $pURIBLDomTable ( 190 | id int(11) NOT NULL AUTO_INCREMENT, 191 | domain text NOT NULL, 192 | strid varchar(32) NOT NULL, 193 | timestamp datetime NOT NULL, 194 | adds mediumint(9) NOT NULL, 195 | hits mediumint(9) NOT NULL, 196 | shortcircuit tinyint(1) NOT NULL, 197 | PRIMARY KEY (id), 198 | UNIQUE KEY uri (domain) USING HASH 199 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 200 | COMMIT; 201 | " 202 | MySQLQuery $SQLDom 203 | } 204 | 205 | Function ExcludeHead ($Body) { 206 | If ($Body -match "") { 207 | Return ($Body -Split "")[1] 208 | } Else { 209 | Return $Body 210 | } 211 | } 212 | 213 | Function GetMainDomain($strDomain) { 214 | $DomainParts = $strDomain.Split(".") 215 | If ($DomainParts.Count -gt 1) { 216 | $TestDomain = $DomainParts[1] 217 | For ($i = 2; $i -lt $DomainParts.Count; $i++) { 218 | $TestDomain = "$TestDomain.$($DomainParts[$i])" 219 | } 220 | } 221 | ElseIf ($a -eq 1) { 222 | $TestDomain = $DomainParts[1] 223 | } 224 | Else { 225 | Return $Null 226 | } 227 | If ($TestDomain -match $PubSufRegEx) { 228 | Return "$($DomainParts[0]).$TestDomain" 229 | } Else { 230 | GetMainDomain $TestDomain 231 | } 232 | } 233 | 234 | Function GetSubFolders ($Folder) { 235 | If ($Folder.SubFolders.Count -gt 0) { 236 | For ($IterateFolder = 0; $IterateFolder -lt $Folder.SubFolders.Count; $IterateFolder++) { 237 | $SubFolder = $Folder.SubFolders.Item($IterateFolder) 238 | If ($SubFolder.Messages.Count -gt 0) {GetMessages $SubFolder} 239 | If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 240 | } 241 | } 242 | } 243 | 244 | Function GetMessages ($Folder) { 245 | $URIHits = 0 246 | $URIsAdded = 0 247 | $LearnedHamMessagesFolder = 0 248 | $LearnedSpamMessagesFolder = 0 249 | 250 | <# Don't proceed if there are no messages #> 251 | If ($Folder.Messages.Count -gt 0) { 252 | 253 | <### INSPECT HAM ###> 254 | 255 | <# Process HAM: only proceed if this is a HAM folder #> 256 | If ($Folder.Name -match $HamFolders) { 257 | 258 | <# Loop through messages #> 259 | For ($IterateMessage = 0; $IterateMessage -lt $Folder.Messages.Count; $IterateMessage++) { 260 | $Message = $Folder.Messages.Item($IterateMessage) 261 | 262 | <# Only process messages younger than $SearchDays #> 263 | If ($Message.InternalDate -gt ((Get-Date).AddDays(-$SearchDays))) { 264 | 265 | <# Record inspected HAM message #> 266 | $TotalHamFedMessages++ 267 | 268 | <# Fill message body properties for matching #> 269 | $Body = $(ExcludeHead $Message.Body) + $(ExcludeHead $Message.HTMLBody) 270 | 271 | <# Test message body against pURI-BL domain regex - if match found then consider the message to be spam #> 272 | If ($Body -match $pURIBLRegEx) { 273 | 274 | <# Log message details #> 275 | Debug "----------------------------" 276 | Debug "pURI-BL hit on HAM message" 277 | Debug "Account : $($hMSAccount.Address)" 278 | Debug "Folder : $($Folder.Name)" 279 | Debug "Message ID : $($Message.ID)" 280 | Debug "File : $($Message.FileName)" 281 | Debug "URI : $URI" 282 | # Debug "URI : $((($URI -Split '://')[0]) + ':// ' + (($URI -Replace 'https?:\/\/','') -Replace '(?\/|\.|\+|\?|\%\w{2}|-)',' ${delim} '))" 283 | 284 | <# Record hit on HAM message #> 285 | $URIHits++ 286 | $HamHits++ 287 | 288 | <# Feed message to SpamC as SPAM #> 289 | If ($FeedSpamC) { 290 | If (!$Simulate) { 291 | Try { 292 | If ((Get-Item $FileName).Length -lt 512000) { 293 | 294 | $SpamC = & cmd /c "`"$SADir\spamc.exe`" -d `"$SAHost`" -p `"$SAPort`" -L spam < `"$Message.FileName`"" 295 | $SpamCResult = Out-String -InputObject $SpamC 296 | 297 | <# If no error, then record as successful #> 298 | If ($SpamCResult -match "Message successfully un/learned") { 299 | $LearnedSpamMessages++ 300 | } 301 | 302 | <# If learning results in error, then throw error and count as unsuccessful #> 303 | If (($SpamCResult -notmatch "Message successfully un/learned") -and ($SpamCResult -notmatch "Message was already un/learned")) { 304 | Throw $SpamCResult 305 | } 306 | 307 | } 308 | } 309 | Catch { 310 | $SpamFedMessageErrors++ 311 | Debug "[ERROR] Feeding SpamC message $($Message.FileName) in $($hMSAccount.Address)" 312 | Debug "[ERROR] $($Error[0])" 313 | $HamFedMessageErrors++ 314 | } 315 | } 316 | } 317 | 318 | <# Move message to junk folder #> 319 | If ($MoveToJunk) { 320 | 321 | <# Find junk folder for account and grab folder ID #> 322 | $JunkFolderID = $Null 323 | For ($IterateIMAPFolders = 0; $IterateIMAPFolders -lt $hMSAccount.IMAPFolders.Count; $IterateIMAPFolders++) { 324 | $hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders) 325 | If ($hMSIMAPFolder.Name -match $SpamFolders) { 326 | [int]$JunkFolderID = $hMSIMAPFolder.ID 327 | Debug "Identified Junk folder as folder ID $JunkFolderID with folder name `"$($hMSIMAPFolder.Name)`"" 328 | Break 329 | } 330 | } 331 | 332 | <# If junk folder found then copy message to junk folder and delete from original folder #> 333 | If ($JunkFolderID) { 334 | Debug "Moving message with ID $($Message.ID) to folder `"$(($hMSAccount.IMAPFolders.ItemByDBID($JunkFolderID)).Name)`"" 335 | If (!$Simulate) { 336 | Try { 337 | $Message.Copy($JunkFolderID) 338 | $Folder.Messages.DeleteByDBID($Message.ID) 339 | } 340 | Catch { 341 | Debug "[ERROR] Could not copy or delete message with ID $($Message.ID)" 342 | Debug "[ERROR] $($Error[0])" 343 | } 344 | } 345 | 346 | <# If junk folder not found then create junk folder, copy message to junk folder and delete from original folder #> 347 | } Else { 348 | Debug "Junk folder not found - create new one then move message to it" 349 | If (!$Simulate) { 350 | Try { 351 | $hMSAccount.IMAPFolders.Add('Spam') 352 | Debug "Added folder `"Spam`" to account $($hMSAccount.Address)" 353 | $NewJunkFolderID = ($hMSAccount.IMAPFolders.ItemByName('Spam')).ID 354 | Debug "Moving message with ID $($Message.ID) to folder $(($hMSAccount.IMAPFolders.ItemByDBID($NewJunkFolderID)).Name)" 355 | $Message.Copy($NewJunkFolderID) 356 | ($hMSAccount.IMAPFolders.ItemByDBID($Folder.ID)).Messages.DeleteByDBID($Message.ID) 357 | } 358 | Catch { 359 | Debug "[ERROR] Could not copy or delete message with ID $($Message.ID)" 360 | Debug "[ERROR] $($Error[0])" 361 | } 362 | } 363 | } 364 | } 365 | } 366 | } 367 | } 368 | } 369 | 370 | <### INSPECT SPAM ###> 371 | 372 | <# Process SPAM: only proceed if this is a spam folder #> 373 | If ($Folder.Name -match $SpamFolders) { 374 | 375 | <# Loop through messages #> 376 | For ($IterateMessage = 0; $IterateMessage -lt $Folder.Messages.Count; $IterateMessage++) { 377 | $Message = $Folder.Messages.Item($IterateMessage) 378 | 379 | <# Only process messages younger than $SearchDays #> 380 | If ($Message.InternalDate -gt ((Get-Date).AddDays(-$SearchDays))) { 381 | Try { 382 | 383 | <# If spam score > delete threshold, then process message #> 384 | If ([int]$Message.HeaderValue('X-hMailServer-Reason-Score') -ge [int]$hMSDeleteThreshold) { 385 | 386 | <# Reset message add counter #> 387 | $MessageAdd = $False 388 | 389 | <# Count messages scored over delete threshold #> 390 | $TotalSpamMessages++ 391 | 392 | <# Fill message body properties for matching #> 393 | $Body = $(ExcludeHead $Message.Body) + $(ExcludeHead $Message.HTMLBody) 394 | 395 | <# Search body for URIs; if found then add to database #> 396 | [RegEx]::Matches($Body, "(\b((https?(:\/\/|%3A%2F%2F))((([a-zA-Z0-9-]+)\.)+[a-zA-Z0-9-]+)(:\d+)?((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)\b)") | ForEach { 397 | 398 | $URIsAdded++ 399 | $TotalURIsAdded++ 400 | $MessageAdd = $True 401 | 402 | $URI = $_.Value 403 | $FullDomain = ([RegEx]::Matches($URI, "(?:^https?)(?::\/\/|%3A%2F%2F)(?:[^@\/\n]+@)?([^:\/%?\n]+)")).Value -Replace "https?:\/\/","" 404 | $MainDomain = GetMainDomain $FullDomain 405 | 406 | If ($MainDomain) { 407 | 408 | <# Log message details #> 409 | Debug "----------------------------" 410 | Debug "pURI-BL URI found in SPAM message" 411 | Debug "Account : $($hMSAccount.Address)" 412 | Debug "Folder : $($Folder.Name)" 413 | Debug "Message ID : $($Message.ID)" 414 | Debug "File : $($Message.FileName)" 415 | Debug "URI : $URI" 416 | # Debug "URI : $((($URI -Split '://')[0]) + ':// ' + (($URI -Replace 'https?:\/\/','') -Replace '(?\/|\.|\+|\?|\%\w{2}|-)',' ${delim} '))" 417 | 418 | If (!$Simulate) { 419 | MySQLQuery "INSERT INTO $pURIBLURITable (uri,strid,domain,timestamp,adds,hits,active) VALUES ('$URI',MD5('$URI'),'$MainDomain',NOW(),1,0,1) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 420 | MySQLQuery "INSERT INTO $pURIBLDomTable (domain,strid,timestamp,adds,shortcircuit) VALUES ('$MainDomain',MD5('$MainDomain'),NOW(),1,0) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 421 | } 422 | } 423 | } 424 | 425 | If ($MessageAdd) { 426 | $MessagesWithAdds++ 427 | } 428 | } 429 | } 430 | Catch { 431 | $SpamFedMessageErrors++ 432 | Debug "[ERROR] Feeding SPAM message $FileName in $($hMSAccount.Address)" 433 | Debug "[ERROR] $($Error[0])" 434 | } 435 | } 436 | } 437 | } 438 | } 439 | 440 | If ($URIHits -gt 0) { 441 | Debug "----------------------------" 442 | Debug "pURI-BL hits on $URIHits HAM message$(Plural $URIHits) fed from $($Folder.Name) in $($hMSAccount.Address)" 443 | } 444 | If ($URIsAdded -gt 0) { 445 | Debug "----------------------------" 446 | Debug "pURI-BL added $URIsAdded URI$(Plural $URIsAdded) from $MessagesWithAdds SPAM message$(Plural $MessagesWithAdds) fed from $($Folder.Name) in $($hMSAccount.Address)" 447 | } 448 | } 449 | 450 | Function FeedpURIBL { 451 | 452 | $Error.Clear() 453 | 454 | $BeginFeedingMessages = Get-Date 455 | Debug "----------------------------" 456 | Debug "Begin processing pURI-BL from messages newer than $SearchDays days" 457 | If ($Simulate) { 458 | Debug "Simulation Mode - Test Run ONLY" 459 | } 460 | 461 | <# Authenticate hMailServer COM #> 462 | $hMS = New-Object -COMObject hMailServer.Application 463 | $AuthAdmin = $hMS.Authenticate("Administrator", $hMSAdminPass) #| Out-Null 464 | If ($AuthAdmin) { 465 | 466 | If ($FeedSpamC) { 467 | $SAHost = $hMS.Settings.AntiSpam.SpamAssassinHost 468 | $SAPort = $hMS.Settings.AntiSpam.SpamAssassinPort 469 | } 470 | 471 | If ([string]::IsNullOrEmpty($HamFolders)) {$HamFolders = "inbox"} 472 | If ([string]::IsNullOrEmpty($SpamFolders)) {$SpamFolders = "spam|junk"} 473 | If ([string]::IsNullOrEmpty($SkipAccounts)) {$SkipAccounts = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | % {[Char]$_}))} 474 | If ([string]::IsNullOrEmpty($SkipDomains)) {$SkipDomains = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | % {[Char]$_}))} 475 | 476 | If ($hMS.Domains.Count -gt 0) { 477 | For ($IterateDomains = 0; $IterateDomains -lt $hMS.Domains.Count; $IterateDomains++) { 478 | $hMSDomain = $hMS.Domains.Item($IterateDomains) 479 | If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch [RegEx]$SkipDomains) -and ($hMSDomain.Accounts.Count -gt 0)) { 480 | For ($IterateAccounts = 0; $IterateAccounts -lt $hMSDomain.Accounts.Count; $IterateAccounts++) { 481 | $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts) 482 | If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch [RegEx]$SkipAccounts) -and ($hMSAccount.IMAPFolders.Count -gt 0)) { 483 | For ($IterateIMAPFolders = 0; $IterateIMAPFolders -lt $hMSAccount.IMAPFolders.Count; $IterateIMAPFolders++) { 484 | $hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders) 485 | GetMessages $hMSIMAPFolder 486 | If ($SearchSubFolders) { 487 | If ($hMSIMAPFolder.SubFolders.Count -gt 0) { 488 | GetSubFolders $hMSIMAPFolder 489 | } 490 | } 491 | } 492 | } 493 | } 494 | } 495 | } 496 | } 497 | 498 | Debug "----------------------------" 499 | Debug "Finished searching messages in $(ElapsedTime $BeginFeedingMessages)" 500 | Debug "----------------------------" 501 | 502 | If ($HamFedMessageErrors -gt 0) { 503 | Debug "Errors feeding HAM to pURI-BL : $HamFedMessageErrors Error$(Plural $HamFedMessageErrors) present" 504 | Email "[ERROR] HAM Inspection : $HamFedMessageErrors Errors present : Check debug log" 505 | } 506 | If ($TotalHamFedMessages -gt 0) { 507 | Debug "pURI-BL hits on $HamHits of $TotalHamFedMessages HAM message$(Plural $TotalHamFedMessages) inspected" 508 | Email "[OK] pURI-BL hits on $HamHits of $TotalHamFedMessages HAM message$(Plural $TotalHamFedMessages) inspected" 509 | } Else { 510 | Debug "No pURI-BL hits on HAM messages younger than $SearchDays days" 511 | Email "[OK] No pURI-BL hits on HAM messages younger than $SearchDays days" 512 | } 513 | 514 | If ($SpamFedMessageErrors -gt 0) { 515 | Debug "Errors feeding SPAM to pURI-BL : $SpamFedMessageErrors Error$(Plural $SpamFedMessageErrors) present" 516 | Email "[ERROR] SPAM SpamC : $SpamFedMessageErrors Errors present : Check debug log" 517 | } 518 | If ($MessagesWithAdds -gt 0) { 519 | Debug "Added $TotalURIsAdded URI$(Plural $TotalURIsAdded) from $TotalSpamMessages SPAM message$(Plural $TotalSpamMessages) found" 520 | Email "[OK] Added $TotalURIsAdded URI$(Plural $TotalURIsAdded) from $TotalSpamMessages SPAM message$(Plural $TotalSpamMessages) found" 521 | } Else { 522 | Debug "No SPAM messages found younger than $SearchDays days to inspect" 523 | Email "[OK] No SPAM messages found younger than $SearchDays days to inspect" 524 | } 525 | 526 | 527 | } Else { 528 | Debug "hMailServer COM application authentication failed - quitting script" 529 | } 530 | } 531 | 532 | Function CustomSARuleFile { 533 | $RuleDir = $SADir + "\etc\spamassassin" 534 | $RuleFile = $SADir + "\etc\spamassassin\pURIBL.cf" 535 | 536 | <# Check for SA local config folder and pURI-BL custom rule file #> 537 | ValidatePath $RuleDir 538 | Try { 539 | If (Test-Path $RuleFile) { 540 | Remove-Item -Force -Path $RuleFile 541 | New-Item $RuleFile 542 | } Else { 543 | New-Item $RuleFile 544 | } 545 | } 546 | Catch { 547 | Debug "[ERROR] Could not create custom spamassassin rule file : Quitting script" 548 | Email "[ERROR] Could not create custom spamassassin rule file : Quitting script" 549 | EmailResults 550 | Exit 551 | } 552 | 553 | <# Fill pURI-BL regex comparators #> 554 | Try { 555 | $N = 0 556 | $Query = " 557 | SELECT domain AS result, strid FROM $pURIBLDomTable WHERE shortcircuit = 1 558 | UNION 559 | SELECT uri AS result, strid FROM $pURIBLURITable WHERE active = 1; 560 | " 561 | MySQLQuery $Query | ForEach { 562 | $StrID = ($_.strid).ToUpper() 563 | $Result = $_.result -Replace "\/","\/" 564 | Write-Output "uri PURIBL_$StrID /$Result/is" | Out-File $RuleFile -Append -Encoding ASCII 565 | Write-Output "score PURIBL_$StrID $Score" | Out-File $RuleFile -Append -Encoding ASCII 566 | Write-Output "describe PURIBL_$StrID pURI-BL personal URI Blacklist" | Out-File $RuleFile -Append -Encoding ASCII 567 | Write-Output " " | Out-File $RuleFile -Append -Encoding ASCII 568 | $N++ 569 | } 570 | } 571 | Catch { 572 | Debug "[ERROR] Could not fill custom spamassassin rule file : Quitting script" 573 | Email "[ERROR] Could not fill custom spamassassin rule file : Quitting script" 574 | EmailResults 575 | Exit 576 | } 577 | } 578 | 579 | Function CheckForUpdates ($VersionNumber) { 580 | Debug "----------------------------" 581 | Debug "Checking for script update at GitHub" 582 | $GitHubVersion = $LocalVersion = $NULL 583 | $GetGitHubVersion = $GetLocalVersion = $False 584 | $GitHubVersionTries = 1 585 | Do { 586 | Try { 587 | $GitHubVersion = [decimal](Invoke-WebRequest -UseBasicParsing -Method GET -URI https://raw.githubusercontent.com/palinkas-jo-reggelt/hMailServer_pURIBL/main/VERSION).Content 588 | $GetGitHubVersion = $True 589 | } 590 | Catch { 591 | Debug "[ERROR] Obtaining GitHub version : Try $GitHubVersionTries : Obtaining version number: $($Error[0])" 592 | } 593 | $GitHubVersionTries++ 594 | } Until (($GitHubVersion -gt 0) -or ($GitHubVersionTries -eq 6)) 595 | If ($GetGitHubVersion) { 596 | If ($VersionNumber -lt $GitHubVersion) { 597 | Debug "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/hMailServer_pURIBL" 598 | If ($UseHTML) { 599 | Email "[INFO] Upgrade to version $GitHubVersion available at GitHub" 600 | } Else { 601 | Email "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/hMailServer_pURIBL" 602 | } 603 | } Else { 604 | Debug "pURI-BL script is latest version: $GitHubVersion" 605 | } 606 | } Else { 607 | If (-not($GetGitHubVersion)) { 608 | Debug "[ERROR] Version test failed : Could not obtain version information from GitHub" 609 | Email "[ERROR] Version check failed" 610 | } Else { 611 | Debug "[ERROR] Version test failed : Unknown reason - file issue at GitHub" 612 | Email "[ERROR] Version check failed" 613 | } 614 | } 615 | } 616 | 617 | <### START SCRIPT ###> 618 | 619 | $StartScript = Get-Date 620 | $DateString = (Get-Date).ToString("yyyy-MM-dd") 621 | $ScriptCreatedFiles = $PSScriptRoot + "\ScriptCreatedFiles" 622 | 623 | <# Clear out error variable #> 624 | $Error.Clear() 625 | 626 | <# Create table structure if tables don't exist #> 627 | CreateTableStructure 628 | 629 | <# Set variables #> 630 | Set-Variable -Name TotalHamFedMessages -Value 0 -Option AllScope 631 | Set-Variable -Name MessagesWithAdds -Value 0 -Option AllScope 632 | Set-Variable -Name HamFedMessageErrors -Value 0 -Option AllScope 633 | Set-Variable -Name SpamFedMessageErrors -Value 0 -Option AllScope 634 | Set-Variable -Name HamHits -Value 0 -Option AllScope 635 | Set-Variable -Name LearnedSpamMessages -Value 0 -Option AllScope 636 | Set-Variable -Name TotalSpamMessages -Value 0 -Option AllScope 637 | Set-Variable -Name TotalURIsAdded -Value 0 -Option AllScope 638 | 639 | <# Check for "ScriptCreatedFiles" folder #> 640 | If (-not(Test-Path $ScriptCreatedFiles -PathType Container)) {md $ScriptCreatedFiles} 641 | 642 | <# Remove trailing slashes from folder locations and validate paths #> 643 | ValidatePath $ScriptCreatedFiles 644 | If ($FeedSpamC) { 645 | $SADir = $SADir -Replace('\\$','') 646 | ValidatePath $SADir 647 | } 648 | 649 | <# Delete old debug files and create new with head info #> 650 | $EmailBody = "$PSScriptRoot\ScriptCreatedFiles\EmailBody.log" 651 | $DebugLog = "$PSScriptRoot\ScriptCreatedFiles\hMailServerDebug.log" 652 | If (Test-Path $EmailBody) {Remove-Item -Force -Path $EmailBody} 653 | If (Test-Path $DebugLog) {Remove-Item -Force -Path $DebugLog} 654 | New-Item $EmailBody 655 | New-Item $DebugLog 656 | Write-Output "::: pURI-BL Offline $(Get-Date -f D) :::" | Out-File $DebugLog -Encoding ASCII -Append 657 | Write-Output " " | Out-File $DebugLog -Encoding ASCII -Append 658 | If ($UseHTML) { 659 | Write-Output " 660 | 661 | 662 | 663 |
:::   pURI-BL Offline   :::
664 |
$(Get-Date -f D)
665 |   666 | " | Out-File $EmailBody -Encoding ASCII -Append 667 | } Else { 668 | Email "::: pURI-BL Offline :::" 669 | Email " $(Get-Date -f D)" 670 | Email " " 671 | } 672 | 673 | <# Fill pURI-BL regex comparators #> 674 | $pRegExQuery = " 675 | SELECT domain AS result FROM $pURIBLDomTable WHERE shortcircuit = 1 676 | UNION 677 | SELECT uri AS result FROM $pURIBLURITable WHERE active = 1; 678 | " 679 | MySQLQuery $pRegExQuery | ForEach { 680 | $pURIBLRegEx = $_.result + "|" + $pURIBLRegEx 681 | } 682 | [RegEx]$pURIBLRegEx = $pURIBLRegEx -Replace "\|$","" 683 | 684 | <# Run pURI-BL script #> 685 | FeedpURIBL 686 | 687 | <# Create/update custom rule file for spamassassin #> 688 | If ($SARuleCF) {If (!$Simulate) {CustomSARuleFile}} 689 | 690 | <# Check for updates #> 691 | CheckForUpdates 7 692 | 693 | <# Finish up and send email #> 694 | Debug "----------------------------" 695 | Debug "pURI-BL Offline routine finished" 696 | Email " " 697 | Email "pURI-BL Offline routine finished" 698 | If ($UseHTML) {Write-Output "
" | Out-File $EmailBody -Encoding ASCII -Append} 699 | EmailResults 700 | -------------------------------------------------------------------------------- /Events/pURIBLtestScript.vbs: -------------------------------------------------------------------------------- 1 | Option Explicit 2 | 3 | '### pURIBL MySQL Table Structure ###' 4 | ' 5 | ' CREATE TABLE IF NOT EXISTS hm_puribluri ( 6 | ' id int(11) NOT NULL AUTO_INCREMENT, 7 | ' strid varchar(32) NOT NULL, 8 | ' uri text NOT NULL, 9 | ' domain varchar(128) NOT NULL, 10 | ' timestamp datetime NOT NULL, 11 | ' adds mediumint(9) NOT NULL, 12 | ' hits mediumint(9) NOT NULL, 13 | ' active tinyint(1) NOT NULL, 14 | ' PRIMARY KEY (id), 15 | ' UNIQUE KEY uri (uri) USING HASH 16 | ' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 17 | ' COMMIT; 18 | 19 | ' CREATE TABLE IF NOT EXISTS hm_puribldom ( 20 | ' id int(11) NOT NULL AUTO_INCREMENT, 21 | ' strid varchar(32) NOT NULL, 22 | ' domain text NOT NULL, 23 | ' timestamp datetime NOT NULL, 24 | ' adds mediumint(9) NOT NULL, 25 | ' hits mediumint(9) NOT NULL, 26 | ' shortcircuit tinyint(1) NOT NULL, 27 | ' PRIMARY KEY (id), 28 | ' UNIQUE KEY uri (domain) USING HASH 29 | ' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 30 | ' COMMIT; 31 | 32 | Dim oMessageBody : oMessageBody = "" & _ 33 | "*ENGINEER TEE SHIRTS*" & _ 34 | "*NEW COLLECTION 2022*" & _ 35 | "" & _ 36 | "" & _ 37 | "" & _ 38 | " " & _ 39 | "" & _ 40 | "" & _ 41 | " " & _ 42 | "" & _ 43 | "" & _ 44 | " " & _ 45 | "" & _ 46 | "" & _ 47 | " " & _ 48 | "" & _ 49 | "" & _ 50 | " " & _ 51 | "" & _ 52 | "" & _ 53 | " " & _ 54 | "" & _ 55 | "" & _ 56 | " " & _ 57 | "" & _ 58 | "" & _ 59 | " " & _ 60 | "" & _ 61 | "" & _ 62 | " " & _ 63 | "" & _ 64 | "" & _ 65 | " " & _ 66 | "" & _ 67 | "" & _ 68 | " " & _ 69 | "" & _ 70 | "" & _ 71 | " " & _ 72 | "" & _ 73 | " " & _ 74 | "" & _ 75 | " " & _ 76 | "" & _ 77 | " " & _ 78 | "" & _ 79 | "" & _ 80 | "**LIMITED TIME OFFER** This is a limited time print that will only be" & _ 81 | "available for 4-10 days." & _ 82 | "*SHIPPING WORLDWIDE!!!*" & _ 83 | "Guaranteed safe and secure checkout via:" & _ 84 | "Paypal | VISA | MASTERCARD" & _ 85 | "Order 2 or more and SAVE on shipping!" & _ 86 | "100% Designed, Shipped, and Printed in the U.S.A." & _ 87 | "*If you're not like receive more mails*" & _ 88 | " *Click Here To Unsubscribe*" & _ 89 | "" 90 | 91 | Dim oMessageHTMLBody : oMessageHTMLBody = "" & _ 92 | "" & _ 95 | "" & _ 96 | "" & _ 97 | "" & _ 98 | "" & _ 109 | "" & _ 110 | "" & _ 129 | "" & _ 137 | "" & _ 138 | "" & _ 140 | "" & _ 142 | " " & _ 143 | " " & _ 144 | " " & _ 145 | " " & _ 146 | " " & _ 258 | " " & _ 259 | "  
" & _ 147 | " " & _ 149 | " " & _ 150 | " " & _ 151 | "
" & _ 153 | " " & _ 154 | " " & _ 155 | " " & _ 156 | "
" & _ 158 | " If you can't read this email, please view it=20" & _ 163 | " online

" & _ 166 | " " & _ 168 | " " & _ 169 | " " & _ 170 | " " & _ 173 | "
" & _ 174 | " " & _ 175 | " " & _ 176 | "
" & _ 187 | " " & _ 189 | " " & _ 190 | " " & _ 191 | " " & _ 194 | "
" & _ 195 | " " & _ 196 | " " & _ 197 | "
" & _ 208 | " " & _ 210 | " " & _ 211 | " " & _ 212 | " " & _ 215 | "
" & _ 216 | " " & _ 217 | " " & _ 218 | "
" & _ 229 | " " & _ 231 | " " & _ 232 | " " & _ 233 | " " & _ 236 | "
" & _ 237 | " " & _ 238 | " " & _ 239 | "
" & _ 248 | " Unsubscribe | Privacy Policy
You have=20" & _ 254 | " subscribed to receive LV email=20" & _ 255 | " communications. 
FR Corporate Address: 417 R ue de=20" & _ 256 | " Babylone, 75007 Paris,=20" & _ 257 | " France

" 261 | 262 | 263 | Dim oMessageHTMLBody2 : oMessageHTMLBody2 = "" & _ 264 | "" & _ 267 | "" & _ 268 | "" & _ 269 | "" & _ 270 | "" & _ 281 | "" & _ 282 | "" & _ 301 | "" & _ 302 | "" & _ 303 | "" & _ 305 | "" & _ 307 | " " & _ 308 | " " & _ 309 | " " & _ 310 | " " & _ 311 | "
" & _ 312 | " " & _ 314 | " " & _ 315 | " " & _ 316 | "
" & _ 318 | " " & _ 319 | " " & _ 320 | " " & _ 321 | "
" & _ 323 | "  View in=20" & _ 327 | " browser

" & _ 328 | " " & _ 330 | " " & _ 331 | " " & _ 332 | " " & _ 335 | "
" & _ 336 | " " & _ 337 | " " & _ 338 | "
" & _ 345 | " " & _ 347 | " " & _ 348 | " " & _ 349 | " " & _ 352 | "
" & _ 353 | " " & _ 354 | " " & _ 355 | "
" & _ 362 | " " & _ 364 | " " & _ 365 | " " & _ 366 | " " & _ 369 | "
" & _ 370 | " " & _ 371 | " " & _ 372 | "
" & _ 380 | " " & _ 382 | " " & _ 383 | " " & _ 384 | " " & _ 387 | "
" & _ 388 | " " & _ 389 | " " & _ 390 | "
" & _ 398 | " " & _ 400 | " " & _ 401 | " " & _ 402 | " " & _ 405 | "
" & _ 406 | " " & _ 407 | " " & _ 408 | "
" & _ 415 | "
To be removed from our email list, please=20" & _ 418 | " click here: unsubscribe

" & _ 422 | " " & _ 423 | "  " & _ 425 | "" & _ 426 | "" & _ 427 | 428 | Private Const hMSPASSWORD = "SuperSecrecthMailServerPassword" 'hMailServer COM password (Administrator password) 429 | Private Const hMSdbPW = "SuperSecretDatabasePassword" 'hMailServer MySQL database user password 430 | Private Const pURIBLURITable = "hm_puribluri" 'pURIBL URI table name 431 | Private Const pURIBLDomTable = "hm_puribldom" 'pURIBL domain table name 432 | Private Const PublicSuffix = "C:\Program Files (x86)\hMailServer\Events\public_suffix_list.vbs" 'Path to public_suffix_list.vbs 433 | Private pURIBLDict : Set pURIBLDict = CreateObject("Scripting.Dictionary") 434 | 435 | Function Lookup(strRegEx, strMatch) : Lookup = False 436 | With CreateObject("VBScript.RegExp") 437 | .Pattern = strRegEx 438 | .Global = False 439 | .MultiLine = True 440 | .IgnoreCase = True 441 | If .Test(strMatch) Then Lookup = True 442 | End With 443 | End Function 444 | 445 | Function oLookup(strRegEx, strMatch, bGlobal) 446 | If strRegEx = "" Then strRegEx = StrReverse(strMatch) 447 | With CreateObject("VBScript.RegExp") 448 | .Pattern = strRegEx 449 | .Global = bGlobal 450 | .MultiLine = True 451 | .IgnoreCase = True 452 | Set oLookup = .Execute(strMatch) 453 | End With 454 | End Function 455 | 456 | Function GetDatabaseObject() 457 | Dim oApp : Set oApp = CreateObject("hMailServer.Application") 458 | Call oApp.Authenticate("Administrator", hMSPASSWORD) 459 | Set GetDatabaseObject = oApp.Database 460 | End Function 461 | 462 | Function Include(sInstFile) 463 | Dim f, s, oFSO 464 | Set oFSO = CreateObject("Scripting.FileSystemObject") 465 | On Error Resume Next 466 | If oFSO.FileExists(sInstFile) Then 467 | Set f = oFSO.OpenTextFile(sInstFile) 468 | s = f.ReadAll 469 | f.Close 470 | ExecuteGlobal s 471 | End If 472 | On Error Goto 0 473 | Set f = Nothing 474 | Set oFSO = Nothing 475 | End Function 476 | 477 | Sub BlackList(oMessage, strMatch, iScore) 478 | Dim i, Done : Done = False 479 | If CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score")) > 0 Then 480 | i = CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score")) 481 | Else 482 | oMessage.HeaderValue("X-hMailServer-Spam") = "YES" 483 | i = 0 484 | End If 485 | oMessage.HeaderValue("X-hMailServer-Blacklist") = "YES" 486 | oMessage.HeaderValue("X-hMailServer-Reason-0") = "BlackListed - (Score: " & iScore & ")" 487 | oMessage.HeaderValue("X-hMailServer-Reason-Score") = iScore + i 488 | i = 1 489 | Do Until Done 490 | If (oMessage.HeaderValue("X-hMailServer-BlackList-" & i) = "") Then 491 | oMessage.HeaderValue("X-hMailServer-BlackList-" & i) = strMatch 492 | Exit Do 493 | Else 494 | i = i + 1 495 | End If 496 | Loop 497 | oMessage.Save 498 | End Sub 499 | 500 | Function GetMainDomain(strDomain) 501 | Dim strRegEx, Match, Matches 502 | Dim TestDomain, DomainParts, a, i, PubSuffMatch 503 | Include(PublicSuffix) 504 | 505 | DomainParts = Split(strDomain,".") 506 | a = UBound(DomainParts) 507 | If a > 1 Then 508 | TestDomain = DomainParts(1) 509 | For i = 2 to a 510 | TestDomain = TestDomain & "." & DomainParts(i) 511 | Next 512 | ElseIf a = 1 Then 513 | TestDomain = DomainParts(1) 514 | Else 515 | Exit Function 516 | End If 517 | 518 | Set Matches = oLookup(PubSufRegEx, TestDomain, False) 519 | For Each Match In Matches 520 | PubSuffMatch = True 521 | Next 522 | 523 | If PubSuffMatch Then 524 | GetMainDomain = DomainParts(0) & "." & TestDomain 525 | Else 526 | GetMainDomain = GetMainDomain(TestDomain) 527 | End If 528 | End Function 529 | 530 | Function ExcludeHead(strStr) 531 | Dim Head 532 | If InStr(strStr, "") Then Head = "" Else If InStr(strStr, "") Then Head = "" Else Head = "" 533 | If InStr(strStr, Head) > 0 Then 534 | ExcludeHead = Right(strStr, ((Len(strStr)) - (InStr(strStr, Head) + 7))) 535 | Else 536 | ExcludeHead = strStr 537 | End If 538 | End Function 539 | 540 | Function pURIBLRegEx(pURIBLDict, pType) : pURIBLRegEx = "" 541 | Dim strData, pID, pRecord, pURL, pSQL 542 | Dim oRecord, oDB : Set oDB = CreateObject("ADODB.Connection") 543 | oDB.Open "Driver={MariaDB ODBC 3.1 Driver}; Server=localhost; Database=hmailserver; User=hmailserver; Password=" & hMSdbPW & ";" 544 | If oDB.State <> 1 Then 545 | EventLog.Write( "pURIBLRegEx - ERROR: Could not connect to database" ) 546 | pURIBLRegEx = "VOID" 547 | Exit Function 548 | End If 549 | 550 | pURIBLDict.RemoveAll 551 | 552 | If pType = "DOM" Then 553 | pSQL = "SELECT * FROM " & pURIBLDomTable & " WHERE shortcircuit = 1;" 554 | pRecord = "domain" 555 | Else 556 | pSQL = "SELECT * FROM " & pURIBLURITable & " WHERE active = 1;" 557 | pRecord = "uri" 558 | End If 559 | 560 | Set oRecord = oDB.Execute(pSQL) 561 | Do Until oRecord.EOF 562 | pID = CStr(Trim(oRecord("id"))) 563 | pURL = CStr(Trim(oRecord(pRecord))) 564 | If (pURL <> "") Then 565 | strData = strData & pURL & "|" 566 | pURIBLDict.Add pID, pURL 567 | End If 568 | oRecord.MoveNext 569 | Loop 570 | 571 | If (Trim(strData) <> "") Then 572 | pURIBLRegEx = Left(strData,Len(strData)-1) 573 | WScript.Echo pURIBLRegEx 574 | Else 575 | ' EventLog.Write("ERROR: pURIBLEDomRegEx: Database returned no records") 576 | WScript.Echo("ERROR: pURIBLRegEx: Database returned no records") 577 | pURIBLRegEx = "VOID" 578 | End If 579 | 580 | Set oRecord = Nothing 581 | oDB.Close 582 | Set oDB = Nothing 583 | End Function 584 | 585 | Function pURIBLStat(pURIBLDict, oMatchValue, pType) 586 | Dim strSQL, oDB, objKey, pTable 587 | Set oDB = CreateObject("ADODB.Connection") 588 | oDB.Open "Driver={MariaDB ODBC 3.1 Driver}; Server=localhost; Database=hmailserver; User=hmailserver; Password=" & hMSdbPW & ";" 589 | If oDB.State <> 1 Then 590 | EventLog.Write( "pURIBLStat - ERROR: Could not connect to database" ) 591 | pURIBLStat = "VOID" 592 | Exit Function 593 | End If 594 | 595 | If pType = "DOM" Then pTable = pURIBLDomTable Else pTable = pURIBLURITable 596 | For Each objKey In pURIBLDict 597 | If Lookup(CStr(pURIBLDict(objKey)), oMatchValue) Then 598 | strSQL = "UPDATE " & pTable & " SET timestamp = NOW(), hits = (hits + 1) WHERE id = '" & CStr(objKey) & "';" 599 | ' Call oDB.Execute(strSQL) 600 | Exit For 601 | End If 602 | Next 603 | Set oDB = Nothing 604 | End Function 605 | 606 | ' WScript.Echo ExcludeHead(oMessageHTMLBody) 607 | 608 | Dim strRegEx, Match, Matches 609 | Dim oMatch, oMatchCollection 610 | Dim Done 611 | 612 | REM - Blacklist on pURIBL 613 | Dim pURIBLBlacklistScore : pURIBLBlacklistScore = 4 'Blacklist Score 614 | Done = False 615 | Do Until Done 616 | 617 | REM - Blacklist on pURIBL Domain match - plain text body 618 | strRegEx = pURIBLRegEx(pURIBLDict, "DOM") 619 | If strRegEx <> "VOID" Then 620 | Set oMatchCollection = oLookup(strRegEx, oMessageBody, False) 621 | For Each oMatch In oMatchCollection 622 | Call pURIBLStat(pURIBLDict, oMatch.Value, "DOM") 623 | ' Call BlackList(oMessage, "pURIBL Dom = '" & oMatch.Value & "'", pURIBLBlacklistScore) 624 | WScript.Echo "DOM hit on oMessageBody: " & oMatch.Value 625 | ' Exit Do 626 | Next 627 | End If 628 | 629 | REM - Blacklist on pURIBL Domain match - HTML body 630 | strRegEx = pURIBLRegEx(pURIBLDict, "DOM") 631 | If strRegEx <> "VOID" Then 632 | Set oMatchCollection = oLookup(strRegEx, oMessageHTMLBody, False) 633 | For Each oMatch In oMatchCollection 634 | Call pURIBLStat(pURIBLDict, oMatch.Value, "DOM") 635 | ' Call BlackList(oMessage, "pURIBL Dom = '" & oMatch.Value & "'", pURIBLBlacklistScore) 636 | WScript.Echo "DOM hit on oMessageHTMLBody: " & oMatch.Value 637 | ' Exit Do 638 | Next 639 | End If 640 | 641 | REM - Blacklist on pURIBL URI match - plain text body 642 | strRegEx = pURIBLRegEx(pURIBLDict, "URI") 643 | If strRegEx <> "VOID" Then 644 | Set oMatchCollection = oLookup(strRegEx, oMessageBody, False) 645 | For Each oMatch In oMatchCollection 646 | Call pURIBLStat(pURIBLDict, oMatch.Value, "URI") 647 | ' Call BlackList(oMessage, "pURIBL URI = '" & oMatch.Value & "'", pURIBLBlacklistScore) 648 | WScript.Echo "URI hit on oMessageBody: " & oMatch.Value 649 | ' Exit Do 650 | Next 651 | End If 652 | 653 | REM - Blacklist on pURIBL URI match - HTML body 654 | strRegEx = pURIBLRegEx(pURIBLDict, "URI") 655 | If strRegEx <> "VOID" Then 656 | Set oMatchCollection = oLookup(strRegEx, oMessageHTMLBody, False) 657 | For Each oMatch In oMatchCollection 658 | Call pURIBLStat(pURIBLDict, oMatch.Value, "URI") 659 | ' Call BlackList(oMessage, "pURIBL URI = '" & oMatch.Value & "'", pURIBLBlacklistScore) 660 | WScript.Echo "URI hit on oMessageHTMLBody: " & oMatch.Value 661 | ' Exit Do 662 | Next 663 | End If 664 | 665 | Done = True 666 | Loop 667 | 668 | WScript.Echo " " 669 | 670 | REM - Populate pURIBL 671 | ' If oMessage.HeaderValue("X-hMailServer-Reason-Score") <> "" Then 672 | ' If CInt(oMessage.HeaderValue("X-hMailServer-Reason-Score") > 6) Then 673 | Dim strSQL, strSQLD, oDB : Set oDB = GetDatabaseObject 674 | ' Dim strRegEx, Match, Matches 675 | Dim strRegExD, MatchD, MatchesD 676 | strRegEx = "(\b((https?(:\/\/|%3A%2F%2F))((([a-zA-Z0-9-]+)\.)+[a-zA-Z0-9-]+)(:\d+)?((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)\b)" 677 | Set Matches = oLookup(strRegEx, ExcludeHead(oMessageBody), True) 678 | For Each Match In Matches 679 | strRegExD = "(?:^https?)(?::\/\/|%3A%2F%2F)(?:[^@\/\n]+@)?([^:\/%?\n]+)" 680 | Set MatchesD = oLookup(strRegExD, Match.SubMatches(0), True) 681 | For Each MatchD In MatchesD 682 | strSQL = "INSERT INTO " & pURIBLURITable & " (uri,domain,timestamp,adds,hits,active) VALUES ('" & Match.SubMatches(0) & "','" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,0,1) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 683 | ' Call oDB.ExecuteSQL(strSQL) 684 | strSQLD = "INSERT INTO " & pURIBLDomTable & " (domain,timestamp,adds,hits,shortcircuit) VALUES ('" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,0,0) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 685 | ' Call oDB.ExecuteSQL(strSQLD) 686 | Next 687 | Next 688 | Set Matches = oLookup(strRegEx, ExcludeHead(oMessageHTMLBody), True) 689 | For Each Match In Matches 690 | strRegExD = "(?:^https?)(?::\/\/|%3A%2F%2F)(?:[^@\/\n]+@)?([^:\/%?\n]+)" 691 | Set MatchesD = oLookup(strRegExD, Match.SubMatches(0), True) 692 | For Each MatchD In MatchesD 693 | strSQL = "INSERT INTO " & pURIBLURITable & " (uri,strid,domain,timestamp,adds,active) VALUES ('" & Match.SubMatches(0) & "',MD5('" & Match.SubMatches(0) & "'),'" & GetMainDomain(MatchD.SubMatches(0)) & "',NOW(),1,1) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 694 | ' Call oDB.ExecuteSQL(strSQL) 695 | strSQLD = "INSERT INTO " & pURIBLDomTable & " (domain,strid,timestamp,adds,hits,shortcircuit) VALUES ('" & GetMainDomain(MatchD.SubMatches(0)) & "',MD5('" & GetMainDomain(MatchD.SubMatches(0)) & "'),NOW(),1,0,0) ON DUPLICATE KEY UPDATE adds=(adds+1),timestamp=NOW();" 696 | ' Call oDB.ExecuteSQL(strSQLD) 697 | Next 698 | Next 699 | ' End If 700 | ' End If 701 | 702 | --------------------------------------------------------------------------------