├── .gitattributes ├── www ├── .htaccess ├── foot.php ├── config.php ├── head.php ├── functions.php ├── login.php ├── stylesheet.css ├── search.php ├── index.php └── account.php ├── RunOnReceive ├── RunOnReceive.bat ├── kill-LimitOutgoing.ps1 ├── hMSAccountUnlock.ps1 ├── RunOnReceive.ps1 ├── hmsLimitOutgoing-DB-Setup.ps1 ├── hmsLimitOutgoing.ps1 ├── pwCommon.ps1 └── pwChange.ps1 ├── README.md └── EventHandlers.vbs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | # CHANGE LAN SUBNET TO YOUR OWN 2 | order deny,allow 3 | deny from all 4 | allow from 127.0.0.1 5 | allow from 192.168.1 -------------------------------------------------------------------------------- /www/foot.php: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /RunOnReceive/RunOnReceive.bat: -------------------------------------------------------------------------------- 1 | REM - FILL IN CORRECT PATH BELOW!!! 2 | Powershell -executionpolicy bypass -File C:\scripts\gammu\runonreceive\RunOnReceive.ps1 %* -------------------------------------------------------------------------------- /www/config.php: -------------------------------------------------------------------------------- 1 | 'localhost', 31 | 'username' => 'hmailserver', 32 | 'password' => 'supersecretpassword', 33 | 'dbname' => 'hmailserver', 34 | 'driver' => 'mysql', 35 | 'port' => '3306', 36 | 'dsn' => 'MariaDB ODBC 3.1 Driver' 37 | ); 38 | 39 | 40 | /* hMailServer COM Authentication 41 | Password for hMailServer "Administrator" 42 | */ 43 | 44 | $hMSAdminPass = "supersecretpassword"; 45 | 46 | ?> -------------------------------------------------------------------------------- /www/head.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | hMailServer Limit Outgoing Messages 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 32 |
33 |
-------------------------------------------------------------------------------- /RunOnReceive/kill-LimitOutgoing.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Message to kill hmsLimitOutgoing.ps1 script 5 | 6 | .DESCRIPTION 7 | Last message of the day, sent in order to kill the tail loop. 8 | 9 | .FUNCTIONALITY 10 | Sends email message to admin. Tail function will trigger IF CURRENT TIME > FINISH TIME statement unless a message comes in to trigger it. 11 | 12 | .NOTES 13 | Run from task scheduler: 14 | - At 23:57:10 Daily 15 | 16 | .EXAMPLE 17 | 18 | 19 | #> 20 | 21 | <### User Variables ###> 22 | $FromAddress = 'notifier.account@gmail.com' 23 | $Recipient = 'admin@mydomain.com' 24 | $SMTPServer = 'smtp.gmail.com' 25 | $SMTPAuthUser = 'notifier.account@gmail.com' 26 | $SMTPAuthPass = 'supersecretpassword' 27 | $SMTPPort = 587 28 | $SSL = 'True' 29 | 30 | <# Include required files #> 31 | Try { 32 | .("$PSScriptRoot\pwCommon.ps1") 33 | } 34 | Catch { 35 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 36 | } 37 | 38 | Function EmailResults($Msg) { 39 | $Subject = "Time to kill Limit Outgoing today" 40 | $Body = $Msg 41 | $Message = New-Object System.Net.Mail.Mailmessage $FromAddress, $Recipient, $Subject, $Body 42 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort 43 | $SMTP.EnableSsl = [System.Convert]::ToBoolean($SSL) 44 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass); 45 | $SMTP.Send($Message) 46 | } 47 | 48 | EmailResults "Time to finish the hmsLimitOutgoing.ps1 script" -------------------------------------------------------------------------------- /www/functions.php: -------------------------------------------------------------------------------- 1 | Authenticate("Administrator", $hMSAdminPass); 24 | $Splitter = explode("@", $account); 25 | $Domain = $Splitter[1]; 26 | $hMSDomainStatus = $hMS->Domains->ItemByName($Domain); 27 | $hMSAccountStatus = $hMSDomainStatus->Accounts->ItemByAddress($account); 28 | $hMSAccountStatus->Active="False"; 29 | $hMSAccountStatus->Save(); 30 | } 31 | 32 | Function enableAccount($account){ 33 | global $hMSAdminPass; 34 | $hMS = new COM("hMailServer.Application"); 35 | $hMS->Authenticate("Administrator", $hMSAdminPass); 36 | $Splitter = explode("@", $account); 37 | $Domain = $Splitter[1]; 38 | $hMSDomainStatus = $hMS->Domains->ItemByName($Domain); 39 | $hMSAccountStatus = $hMSDomainStatus->Accounts->ItemByAddress($account); 40 | $hMSAccountStatus->Active="True"; 41 | $hMSAccountStatus->Save(); 42 | } 43 | 44 | ?> 45 | -------------------------------------------------------------------------------- /RunOnReceive/hMSAccountUnlock.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | hMailServer Account Unlock 5 | 6 | .DESCRIPTION 7 | Unlock hMailServer email account via SMS after lock due to inactivity. 8 | 9 | .FUNCTIONALITY 10 | 1) Recieves UNLOCK command by SMS 11 | 2) Removes disconnect switch from account 12 | 13 | .PARAMETER rorNum 14 | Specifies the SMS mobile number. 15 | 16 | .PARAMETER rorMsg 17 | Specifies the SMS message. 18 | 19 | .NOTES 20 | 21 | 22 | .EXAMPLE 23 | 24 | 25 | #> 26 | 27 | Param( 28 | [ValidatePattern('^\+\d{11}$|^\d{10}$')] 29 | [string]$rorNum, 30 | [ValidatePattern('unlock')] 31 | [string]$rorMsg 32 | ) 33 | 34 | <# Include required files #> 35 | Try { 36 | .("$PSScriptRoot\pwCommon.ps1") 37 | } 38 | Catch { 39 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 40 | } 41 | 42 | <# Set script variables from parameters #> 43 | [regex]$RegExNum = "[0-9]{10}$" 44 | $Num = ($RegExNum.Matches($rorNum)).Value 45 | 46 | <# Get username from mobile number #> 47 | $Query = "SELECT account, accountlock FROM hm_accounts_mobile WHERE mobilenumber = '$Num';" 48 | MySQLQuery $Query | ForEach { 49 | $Account = $_.account 50 | $Lock = $_.accountlock 51 | 52 | If ($Lock -eq 1){ 53 | <# Revert switch back to 0 to unlock account #> 54 | $Query = "UPDATE hm_accounts_mobile SET accountlock = 0 WHERE account = '$Account';" 55 | MySQLQuery $Query 56 | 57 | <# Send account unlocked notification message #> 58 | $Msg = "Your account ($Account) has been unlocked. You may now login." 59 | SendSMS $Num $Msg 60 | 61 | <# Notify admin #> 62 | $AdminMsg = "User $Account account unlocked by user (lock triggered by inactivity)." 63 | SendSMS $AdminNumber $AdminMsg 64 | 65 | <# Reset last logon to NOW #> 66 | $Query = "UPDATE hm_accounts_mobile SET lastlogontime = NOW() WHERE account = '$Account';" 67 | MySQLQuery $Query 68 | } 69 | } -------------------------------------------------------------------------------- /RunOnReceive/RunOnReceive.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Gammu RunOnReceive 5 | 6 | .DESCRIPTION 7 | Script to direct Gammu RunOnReceive to appropriate actions 8 | 9 | .FUNCTIONALITY 10 | 1) Receives Gammu database message ID from RunOnReceive.bat 11 | 2) Retrieve sender number and message from database 12 | 3) Direct sender number and message to appropriate script 13 | 14 | .PARAMETER ID 15 | Specifies the gammu database inbox ID number. Used for retrieval of sender number and message. 16 | 17 | .NOTES 18 | Database variables and function are for GAMMU database only!!! (hMailServer database variables and function separately located in pwCommon.ps1) 19 | 20 | .EXAMPLE 21 | 22 | 23 | #> 24 | 25 | Param([string]$ID) 26 | 27 | 28 | <### GAMMU MYSQL VARIABLES ###> 29 | $SQLAdminUserName = 'gammu' 30 | $SQLAdminPassword = 'supersecretpassword' 31 | $SQLDatabase = 'gammu' 32 | $SQLHost = '127.0.0.1' 33 | $SQLPort = 3306 34 | $SQLSSL = 'none' 35 | 36 | <# Gammu Database Function #> 37 | Function MySQLQuery($Query) { 38 | $Today = (Get-Date).ToString("yyyyMMdd") 39 | $DBErrorLog = "$PSScriptRoot\$Today-DBError.log" 40 | $ConnectionString = "server=" + $SQLHost + ";port=" + $SQLPort + ";uid=" + $SQLAdminUserName + ";pwd=" + $SQLAdminPassword + ";database=" + $SQLDatabase + ";SslMode=" + $SQLSSL + ";" 41 | Try { 42 | [void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data") 43 | $Connection = New-Object MySql.Data.MySqlClient.MySqlConnection 44 | $Connection.ConnectionString = $ConnectionString 45 | $Connection.Open() 46 | $Command = New-Object MySql.Data.MySqlClient.MySqlCommand($Query, $Connection) 47 | $DataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($Command) 48 | $DataSet = New-Object System.Data.DataSet 49 | $RecordCount = $dataAdapter.Fill($dataSet, "data") 50 | $DataSet.Tables[0] 51 | } 52 | Catch { 53 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to run query : $query `n$Error[0]" | Out-File $DBErrorLog -append 54 | } 55 | Finally { 56 | $Connection.Close() 57 | } 58 | } 59 | 60 | <# Get message from database #> 61 | $Query = "SELECT TextDecoded, SenderNumber FROM inbox WHERE ID = '$ID'" 62 | MySQLQuery $Query | ForEach { 63 | $rorNum = $_.SenderNumber 64 | $rorMsg = $_.TextDecoded 65 | } 66 | 67 | If ($rorMsg -match 'pw[\s](change|mine|new|random)'){& "$PSScriptRoot\pwChange.ps1" -rorNum $rorNum -rorMsg $rorMsg} 68 | ElseIf ($rorMsg -match 'unlock'){& "$PSScriptRoot\hMSAccountUnlock.ps1" -rorNum $rorNum -rorMsg $rorMsg} 69 | Else { exit } -------------------------------------------------------------------------------- /www/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 | -------------------------------------------------------------------------------- /RunOnReceive/hmsLimitOutgoing-DB-Setup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Setup Database for hMailServer Limit Outgoing Messages / Two Factor Project 5 | 6 | .DESCRIPTION 7 | Setup Database for hMailServer Limit Outgoing Messages / Two Factor Project 8 | 9 | .FUNCTIONALITY 10 | 1) Run once from powershell console to setup database, create scheduled task and export users to csv (script creates Accounts.csv located in same folder as script) 11 | 2) Manually edit Accounts.csv to add mobile numbers 12 | 3) Run again to fill hm_accounts_mobile with account and mobilenumber data from Accounts.csv 13 | 14 | .NOTES 15 | Column "lastlogontime" gets filled with current datetime so all users don't get blasted with requests to unlock account the next time they log in. 16 | 17 | .EXAMPLE 18 | 19 | 20 | #> 21 | 22 | <# Include required files #> 23 | Try { 24 | .("$PSScriptRoot\pwCommon.ps1") 25 | } 26 | Catch { 27 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 28 | } 29 | 30 | $AccountsFile = "$PSScriptRoot\Accounts.csv" 31 | 32 | <# If Accounts.csv doesn't exist, that means script hasn't run yet, so setup database and create/fill Accounts.csv with account data #> 33 | If (-not(Test-Path $AccountsFile)){ 34 | 35 | $Query = " 36 | CREATE TABLE IF NOT EXISTS hm_accounts_mobile ( 37 | account varchar(192) NOT NULL, 38 | mobilenumber varchar(10) NOT NULL, 39 | accountlock int(1) NOT NULL, 40 | accountdisabled int(1) NOT NULL, 41 | lastlocktime datetime NOT NULL DEFAULT '1969-12-31 23:59:59', 42 | lastlogontime datetime NOT NULL DEFAULT '1969-12-31 23:59:59', 43 | lastmessagetime datetime NOT NULL DEFAULT '1969-12-31 23:59:59', 44 | messagecount int(3) NOT NULL, 45 | initpw int(1) NOT NULL, 46 | PRIMARY KEY (account) 47 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 48 | COMMIT; 49 | " 50 | MySQLQuery $Query 51 | 52 | <# Create header in Accounts.csv #> 53 | Write-Output "account,mobilenumber" | Out-File $AccountsFile -Append 54 | 55 | <# Fill account data into Accounts.csv #> 56 | $Query = "SELECT accountaddress FROM hm_accounts;" 57 | MySQLQuery $Query | ForEach { 58 | $Account = $_.accountaddress 59 | Write-Output "$Account," | Out-File $AccountsFile -Append 60 | } 61 | 62 | <# If Accounts.csv exists, then database is created and presumeably the csv file has been edited to include mobile numbers #> 63 | } Else { 64 | 65 | <# Fill hm_accounts_mobile with data from csv #> 66 | $ImportMobileNumbers = Import-CSV -Path $AccountsFile -Delimiter "," -Header account, mobilenumber 67 | $ImportMobileNumbers | ForEach { 68 | $Account = $_.account 69 | $MobileNumber = $_.mobilenumber 70 | If ($Account -notmatch "account"){ 71 | $Query = "INSERT INTO hm_accounts_mobile (account,mobilenumber,lastlogontime) VALUES ('$Account','$MobileNumber',NOW());" 72 | MySQLQuery $Query 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /www/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fefefe; 3 | font-family: "Roboto"; 4 | font-size: 12pt; 5 | } 6 | 7 | a:link, a:active, a:visited { 8 | color: #FF0000; 9 | text-transform: underline; 10 | } 11 | 12 | a:hover { 13 | color: #FF0000; 14 | text-transform: none; 15 | } 16 | 17 | .header { 18 | position: fixed; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | color: #000; 23 | background: #fefefe; 24 | z-index: 1; 25 | overflow: hidden; 26 | text-align:center; 27 | } 28 | 29 | .header h1 { 30 | font-size:25px; 31 | font-weight:normal; 32 | margin:0 auto; 33 | } 34 | 35 | .header h2 { 36 | font-size:15px; 37 | font-weight:normal; 38 | margin:0 auto; 39 | } 40 | 41 | .wrapper { 42 | max-width: 920px; 43 | position: relative; 44 | margin: 30px auto 30px auto; 45 | padding-top: 20px; 46 | } 47 | 48 | .clear { 49 | clear: both; 50 | } 51 | 52 | .banner { 53 | width: 100%; 54 | } 55 | 56 | .headlinks { 57 | max-width: 720px; 58 | position:relative; 59 | margin: 0px auto; 60 | } 61 | 62 | .headlinkwidth { 63 | width: 100%; 64 | min-width: 300px; 65 | position:relative; 66 | margin: 0 auto; 67 | } 68 | 69 | .headlinks a:link, a:active, a:visited { 70 | color: #FF0000; 71 | text-transform: underline; 72 | } 73 | 74 | .headlinks a:hover { 75 | color: #FF0000; 76 | text-transform: none; 77 | } 78 | 79 | .section { 80 | padding: 5px 0 15px 0; 81 | margin: 0; 82 | } 83 | 84 | .section h2 { 85 | font-size:16px; 86 | font-weight:bold; 87 | text-align:left; 88 | } 89 | 90 | .section h3 { 91 | font-size:16px; 92 | font-weight:bold; 93 | } 94 | 95 | .secleft { 96 | float: left; 97 | width: 49%; 98 | padding-right: 3px; 99 | } 100 | 101 | .secright { 102 | float: right; 103 | width: 49%; 104 | padding-left: 3px; 105 | } 106 | 107 | .secmap { 108 | float: none; 109 | width: 920px; 110 | height: 600px; 111 | padding: 0 0 10px 0; 112 | text-align: center; 113 | } 114 | 115 | table.section { 116 | border-collapse: collapse; 117 | border: 1px solid black; 118 | border-spacing: 10px 119 | width: 100%; 120 | font-size: 10pt; 121 | } 122 | 123 | table.section tr:nth-child(even) { 124 | background-color: #F8F8F8; 125 | padding: 2px 0 2px 0; 126 | } 127 | 128 | table.section th, table.section td { 129 | border: 1px solid black; 130 | padding: 2px 0 2px 0; 131 | } 132 | 133 | .footer { 134 | width: 100%; 135 | text-align: center; 136 | } 137 | 138 | ul { 139 | list-style-type: none; 140 | padding: 0; 141 | } 142 | 143 | li { 144 | padding: 0; 145 | display: inline; 146 | } 147 | 148 | @media only screen and (max-width: 629px) { 149 | .secleft { 150 | float: none; 151 | width: 100%; 152 | padding: 0 0 10px 0; 153 | text-align: left; 154 | } 155 | .secright { 156 | float: none ; 157 | width: 100% ; 158 | } 159 | .secmap { 160 | float: none; 161 | width: 95%; 162 | height: 220px; 163 | padding: 0 0 10px 0; 164 | text-align: center; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hMailServer-Limit-Outgoing-Messages 2 | 3 | Limit Daily Outgoing Messages on hMailServer 4 | *and* 5 | Require "It's Really Me" after inactivity period 6 | 7 | 8 | Two projects in one. 9 | 10 | **Limit Daily Outgoing Messages:** 11 | * Tail AWStats log and count outgoing messages 12 | * Update database with daily message count per user 13 | * If user sends more than N messages in a day, disable account and force password change 14 | * Enable account after user successfully changes strong password 15 | * All communication with user via SMS while account is disabled 16 | 17 | **Inactivity Based Two Factor Auth** 18 | * User logons are counted (dated) 19 | * If last logon more than N days in the past, disable account and require user to prove identity 20 | * Proof of identity is simple response to SMS: UNLOCK 21 | * All communication with user via SMS while account is disabled 22 | 23 | 24 | # System Requirements 25 | - Working hMailServer (Email) 5.7.0 OR one of RvdH's custom builds with OnClientLogon 26 | - Working Gammu (SMS) with functional gammu-smsd-inject AND configured RunOnReceive 27 | - Gammu and hMailServer running on MySQL (connection strings and queries are for MySQL only) 28 | 29 | 30 | # Instructions 31 | 32 | **For Gammu:** 33 | 34 | 1) Place powershell scripts in your runonrecieve folder, or create one. I've included RunOnReceive.bat and RunOnReceive.ps1 if you have not configured Gammu runonrecieve. You must configure smsdrc with the runonreceive path to RunOnReceive.bat. If you already have a working runonreceive with your own scripts, modify them per the included RunOnReceive.ps1. 35 | 2) Change the path in RunOnReceive.bat 36 | 3) Change the user variables in RunOnReceive.ps1 37 | 4) Change the user variables in pwCommon.ps1 38 | 39 | 40 | **For hMailServer:** 41 | 42 | 1) Copy the contents of EventHandlers.vbs into your hMailServer EventHandlers.vbs (default location: C:\Program Files (x86)\hMailServer\Events). 43 | 2) In Sub GetTwoFactorInfo, change the mysql connection string according to your variables. 44 | 3) In Sub OnClientLogon, read the notes and change the variables according to your needs. 45 | 4) If you DON'T WANT to use the inactivity two factor portion of the project, simply skip the above (do not modify EventHandlers.vbs). 46 | 47 | 48 | **To Setup Database:** 49 | 50 | 1) After updating the user variables, run hmsLimitOutgoing-DB-Setup.ps1. This will create table hm_accounts_mobile and will also query existing hm_accounts for all user accounts and export them to Accounts.csv. 51 | 2) Manually update Accounts.csv to include mobile numbers for each account. If the account is not a real person's account, leave it 0. The two-factor should only be used for real people that can respond to SMS, not machine accounts for scripts and scanners, etc. 52 | 3) Run hmsLimitOutgoing-DB-Setup.ps1 again to update the hm_accounts_mobile with the data you manually entered. 53 | 54 | 55 | **For Tailing AWStats Log:** 56 | 57 | 1) Create a scheduled task to run hmsLimitOutgoing.ps1 at startup !!!AND!!! at 12:01 AM daily (one task, two triggers). Script runs daily between 00:01 and 23:57. 58 | 2) Create a scheduled task to run kill-LimitOutgoing.ps1 at 23:57 daily. The hmsLimitOutgoing.ps1 script contains a foreach loop that tails the awstats log. There is no good way to break from this loop, so there is an IF statement at the end: if current time > 23:56 then exit. The problem is that unless there is a new awstats entry after 23:56, nothing will trigger the IF statement. This may not be a problem on a busy server. kill-LimitOutgoing.ps1 simply sends a message to the hMailServer administrator, which then gets added to awstats, which in turn triggers the IF statement allowing the script to quit on time. Without this trigger, the next day's 00:01 startup may not occur with an error in task scheduler. 59 | 60 | 61 | **For Web Admin:** 62 | 63 | Copy www folder into php accessible webserver and change variables in config.php. Warning: uses hMailServer COM: you must extension=php_com_dotnet.dll in php.ini in order to enable/disable accounts. 64 | -------------------------------------------------------------------------------- /www/search.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
"; 19 | echo "
"; 20 | echo "
"; 21 | echo " "; 22 | echo " "; 23 | echo "
"; 24 | echo "
"; 25 | 26 | echo "
"; 27 | 28 | $no_of_records_per_page = 20; 29 | $offset = ($page-1) * $no_of_records_per_page; 30 | 31 | $total_pages_sql = $pdo->prepare(" 32 | SELECT Count( * ) AS count 33 | FROM hm_accounts_mobile 34 | WHERE account LIKE '%".$search."%' 35 | "); 36 | $total_pages_sql->execute(); 37 | $total_rows = $total_pages_sql->fetchColumn(); 38 | $total_pages = ceil($total_rows / $no_of_records_per_page); 39 | 40 | $sql = $pdo->prepare(" 41 | SELECT 42 | account, 43 | mobilenumber, 44 | lastlocktime, 45 | lastlogontime, 46 | lastmessagetime, 47 | messagecount, 48 | accountdisabled, 49 | accountlock 50 | FROM hm_accounts_mobile 51 | WHERE account LIKE '%".$search."%' 52 | ORDER BY DATE(lastmessagetime) DESC, messagecount DESC 53 | LIMIT ".$offset.", ".$no_of_records_per_page 54 | ); 55 | $sql->execute(); 56 | 57 | if ($search==""){ 58 | $search_res=""; 59 | } else { 60 | $search_res=" for search term \"".$search."\""; 61 | } 62 | 63 | if ($total_pages < 2){ 64 | $pagination = ""; 65 | } else { 66 | $pagination = "(Page: ".number_format($page)." of ".number_format($total_pages).")"; 67 | } 68 | 69 | if ($total_rows == 1){$singular = '';} else {$singular= 's';} 70 | if ($total_rows == 0){ 71 | if ($search == ""){ 72 | echo "Please enter a search term"; 73 | } else { 74 | echo "No results ".$search_res; 75 | } 76 | } else { 77 | echo "Results ".$search_res.": ".number_format($total_rows)." Account".$singular." ".$pagination."
"; 78 | echo " 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | "; 89 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 90 | echo ""; 91 | echo ""; 92 | echo ""; 93 | echo ""; 94 | echo ""; 95 | if ($row['accountlock']==0){ 96 | echo ""; 97 | } elseif ($row['accountlock']==1){ 98 | echo ""; 99 | } else { 100 | echo ""; 101 | } 102 | echo ""; 103 | } 104 | echo "
SEARCH OR BROWSE USER ACCOUNTS
AccountLast LogonLast Message# MsgsLocked
".$row['account']."".date("y/n/j G:i:s", strtotime($row['lastlogontime']))."".date("y/n/j G:i:s", strtotime($row['lastmessagetime']))."".$row['messagecount']."NoYESERR
"; 105 | 106 | if ($total_pages == 1){echo "";} 107 | else { 108 | echo ""; 114 | } 115 | } 116 | ?> 117 | 118 |
119 |
120 | 121 | -------------------------------------------------------------------------------- /RunOnReceive/hmsLimitOutgoing.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | Tail hMailServer AWStats Log & Count Outgoing Messages 5 | 6 | .DESCRIPTION 7 | Tail hMailServer AWStats Log & Count Outgoing Messages 8 | 9 | .FUNCTIONALITY 10 | Tails AWStats log, updates database with daily message count. 11 | 12 | .NOTES 13 | Run from task scheduler with TWO triggers: 14 | - At Startup 15 | - At 12:01 AM Daily 16 | 17 | .EXAMPLE 18 | 19 | 20 | #> 21 | 22 | <# Include required files #> 23 | Try { 24 | .("$PSScriptRoot\pwCommon.ps1") 25 | } 26 | Catch { 27 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 28 | } 29 | 30 | <# Check to see if hMailServer is running. If not, quit. MySQL is a dependency of hMailServer service so you're actually checking both. #> 31 | <# Prevents scheduled task failures at bootup. #> 32 | Do { 33 | If ((get-service hMailServer).Status -ne 'Running') { 34 | $Running = "NO" 35 | Start-Sleep -seconds 30 36 | } Else { 37 | $Running = "YES" 38 | } 39 | } Until ($Running = "YES") 40 | 41 | <# RegEx to find email address in a string #> 42 | [regex]$RegexEmail = "[A-Za-z0-9!.#$%&'*+\/=?^_`{|}~-]+@([A-Za-z0-9-]{2,63}\.){1,10}[A-Za-z0-9-]{2,12}" 43 | 44 | <# Tail log and convert log lines to objects #> 45 | Get-Content "$hMSLogFolder\hmailserver_awstats.log" -Wait -Tail 1 | ConvertFrom-String -Delimiter "`t" -PropertyNames TimeStamp, Sender, Recipient, ConnectionSender, ConnectionRecipient, Protocol, QuestionMark, StatusCode, MessageSize | ForEach { 46 | 47 | $Sender = $_.Sender 48 | $MsgTimeStamp = $_.TimeStamp 49 | 50 | <# Clear out variables in loop #> 51 | $Account = $UpdateQuery = $Query = $Account = $MobileNumber = $LastMessageTime = $MessageCount = $Msg = $NULL 52 | 53 | <# Check if sender is local by checking if hMailServer user exists #> 54 | $Query = "SELECT account, mobilenumber FROM hm_accounts_mobile WHERE account = '$Sender';" 55 | MySQLQuery $Query | ForEach { 56 | $Account = $_.account 57 | $MobileNumber = $_.mobilenumber 58 | } 59 | 60 | <# If user local, get last message time #> 61 | If ($Sender -match $Account){ 62 | $Query = "SELECT lastmessagetime FROM hm_accounts_mobile WHERE account = '$Account';" 63 | MySQLQuery $Query | ForEach { 64 | $LastMessageTime = Get-Date $_.lastmessagetime 65 | } 66 | 67 | <# If last message today, then update count = existing count + 1; otherwise this is the first message of the day #> 68 | If ($LastMessageTime -lt ([datetime]::Today)){ 69 | $UpdateQuery = "UPDATE hm_accounts_mobile SET lastmessagetime = NOW(), messagecount = 1 WHERE account = '$Account';" 70 | } Else { 71 | $UpdateQuery = "UPDATE hm_accounts_mobile SET lastmessagetime = NOW(), messagecount = (messagecount + 1) WHERE account = '$Account';" 72 | } 73 | MySQLQuery $UpdateQuery 74 | 75 | <# Get total count for user #> 76 | $Query = "SELECT messagecount FROM hm_accounts_mobile WHERE account = '$Account';" 77 | MySQLQuery $Query | ForEach { 78 | $MessageCount = $_.messagecount 79 | } 80 | 81 | <# If message count exceeded, disable account and send SMS notification requiring password change #> 82 | If ($MessageCount -gt $MsgLimit){ 83 | 84 | <# Only send notification to user if account has mobile number #> 85 | If ($MobileNumber -ne 0){ 86 | 87 | <# Disable account #> 88 | DisableAccount $Account 89 | 90 | <# Send notification #> 91 | $Msg = "Security notice from $(((Get-Culture).TextInfo).ToTitleCase(($Account).Split('@')[1])) Mail Server: Your account ($Account) has exceeded $MsgLimit outgoing messages today and has been disabled to prevent abuse. In order to enable your account you are required to change your password. Reply PW CHANGE to initiate process." 92 | SendSMS $MobileNumber $Msg 93 | } 94 | 95 | <# Notify admin #> 96 | $AdminMsg = "Outgoing message limit exceeded for $Account - account disabled." 97 | SendSMS $AdminNumber $AdminMsg 98 | } 99 | } 100 | <# Quit script at 23:57 in order to load next day's log (initiated by scheduled task at 00:01) #> 101 | If ((Get-Date -format HH:mm) -gt "23:56") { Exit } 102 | } -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
"; 19 | echo "
"; 20 | 21 | $total_pages_sql = $pdo->prepare(" 22 | SELECT Count( * ) AS count 23 | FROM hm_accounts_mobile 24 | WHERE messagecount > 0 AND DATE(lastmessagetime) = DATE(NOW()) 25 | "); 26 | $total_pages_sql->execute(); 27 | $total_rows = $total_pages_sql->fetchColumn(); 28 | 29 | $sql = $pdo->prepare(" 30 | SELECT 31 | account, 32 | mobilenumber, 33 | lastlocktime, 34 | lastlogontime, 35 | lastmessagetime, 36 | messagecount, 37 | accountdisabled, 38 | accountlock 39 | FROM hm_accounts_mobile 40 | WHERE messagecount > 0 AND DATE(lastmessagetime) = DATE(NOW()) 41 | ORDER BY DATE(lastmessagetime) DESC, messagecount DESC 42 | "); 43 | $sql->execute(); 44 | 45 | echo " 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | "; 56 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 57 | echo ""; 58 | echo ""; 59 | echo ""; 60 | echo ""; 61 | echo ""; 62 | if (($row['accountlock']==0) && ($row['accountdisabled']==0)){ 63 | echo ""; 64 | } elseif (($row['accountlock']==1) || ($row['accountdisabled']==1)){ 65 | echo ""; 66 | } else { 67 | echo ""; 68 | } 69 | echo ""; 70 | } 71 | echo "
TODAY'S MESSAGES: ".$total_rows."
AccountLast LogonLast Message# MsgsLocked
".$row['account']."".date("y/n/j G:i:s", strtotime($row['lastlogontime']))."".date("y/n/j G:i:s", strtotime($row['lastmessagetime']))."".$row['messagecount']."NoYESERR
"; 72 | echo "


"; 73 | 74 | $total_pages_yesql = $pdo->prepare(" 75 | SELECT Count( * ) AS count 76 | FROM hm_accounts_mobile 77 | WHERE accountlock = 1 78 | "); 79 | $total_pages_yesql->execute(); 80 | $total_yes_rows = $total_pages_yesql->fetchColumn(); 81 | 82 | $yesql = $pdo->prepare(" 83 | SELECT 84 | account, 85 | mobilenumber, 86 | lastlocktime, 87 | lastlogontime, 88 | lastmessagetime, 89 | messagecount, 90 | accountdisabled, 91 | accountlock 92 | FROM hm_accounts_mobile 93 | WHERE accountlock = 1 94 | ORDER BY DATE(lastlocktime) DESC 95 | "); 96 | $yesql->execute(); 97 | 98 | echo " 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | "; 109 | while($row = $yesql->fetch(PDO::FETCH_ASSOC)){ 110 | echo ""; 111 | echo ""; 112 | echo ""; 113 | echo ""; 114 | echo ""; 115 | if (($row['accountlock']==0) && ($row['accountdisabled']==0)){ 116 | echo ""; 117 | } elseif (($row['accountlock']==1) || ($row['accountdisabled']==1)){ 118 | echo ""; 119 | } else { 120 | echo ""; 121 | } 122 | echo ""; 123 | } 124 | echo "
LOCKED ACCOUNTS: ".$total_yes_rows."
AccountLast LogonLast Message# MsgsLocked
".$row['account']."".date("y/n/j G:i:s", strtotime($row['lastlogontime']))."".date("y/n/j G:i:s", strtotime($row['lastmessagetime']))."".$row['messagecount']."NoYESERR
"; 125 | echo "


"; 126 | 127 | $total_pages_disql = $pdo->prepare(" 128 | SELECT Count( * ) AS count 129 | FROM hm_accounts_mobile 130 | WHERE accountdisabled = 1 131 | "); 132 | $total_pages_disql->execute(); 133 | $total_dis_rows = $total_pages_disql->fetchColumn(); 134 | 135 | $disql = $pdo->prepare(" 136 | SELECT 137 | account, 138 | mobilenumber, 139 | lastlocktime, 140 | lastlogontime, 141 | lastmessagetime, 142 | messagecount, 143 | accountdisabled, 144 | accountlock 145 | FROM hm_accounts_mobile 146 | WHERE accountdisabled = 1 147 | ORDER BY DATE(lastmessagetime) DESC 148 | "); 149 | $disql->execute(); 150 | 151 | echo " 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | "; 162 | while($row = $disql->fetch(PDO::FETCH_ASSOC)){ 163 | echo ""; 164 | echo ""; 165 | echo ""; 166 | echo ""; 167 | echo ""; 168 | if (($row['accountlock']==0) && ($row['accountdisabled']==0)){ 169 | echo ""; 170 | } elseif (($row['accountlock']==1) || ($row['accountdisabled']==1)){ 171 | echo ""; 172 | } else { 173 | echo ""; 174 | } 175 | echo ""; 176 | } 177 | echo "
DISABLED ACCOUNTS: ".$total_dis_rows."
AccountLast LogonLast Message# MsgsLocked
".$row['account']."".date("y/n/j G:i:s", strtotime($row['lastlogontime']))."".date("y/n/j G:i:s", strtotime($row['lastmessagetime']))."".$row['messagecount']."NoYESERR
"; 178 | ?> 179 | 180 |
181 |
182 | 183 | -------------------------------------------------------------------------------- /EventHandlers.vbs: -------------------------------------------------------------------------------- 1 | Function Lookup(strRegEx, strMatch) : Lookup = False 2 | With CreateObject("VBScript.RegExp") 3 | .Pattern = strRegEx 4 | .Global = False 5 | .MultiLine = True 6 | .IgnoreCase = True 7 | If .Test(strMatch) Then Lookup = True 8 | End With 9 | End Function 10 | 11 | Function oLookup(strRegEx, strMatch, bGlobal) 12 | If strRegEx = "" Then strRegEx = StrReverse(strMatch) 13 | With CreateObject("VBScript.RegExp") 14 | .Pattern = strRegEx 15 | .Global = bGlobal 16 | .MultiLine = True 17 | .IgnoreCase = True 18 | Set oLookup = .Execute(strMatch) 19 | End With 20 | End Function 21 | 22 | Function RemoveHTML(strText) 23 | Dim strRegEx, Match, Matches 24 | 25 | strRegEx = "<[^>]+>| |<|>|"|&|\s{2,}|v\\\:\*|o\\\:\*|w\\\:\*|\.shape|\{behavior:url\(\#default\#VML\)\;\}" 26 | Set Matches = oLookup(strRegEx, strText, False) 27 | For Each Match In Matches 28 | strText = Replace(strText, Match.Value, " ") 29 | Next 30 | 31 | strRegEx = "[\s]{2,}" 32 | Set Matches = oLookup(strRegEx, strText, False) 33 | For Each Match In Matches 34 | strText = Replace(strText, Match.Value, " ") 35 | Next 36 | 37 | RemoveHTML = strText 38 | End Function 39 | 40 | Function SendSMS(SMSNumber, sMSG) 41 | Dim rc, WshShell, strRegEx, Match, Matches, sMSGLen 42 | 43 | REM - put default mobile number here (administrator's number) 44 | If SMSNumber = Empty Then SMSNumber = "1234567890" 45 | 46 | sMSG = RemoveHTML(sMSG) 47 | 48 | REM - replace line breaks 49 | strRegEx = "(\r\n|\r|\n)" 50 | Set Matches = oLookup(strRegEx, sMSG, False) 51 | For Each Match In Matches 52 | sMSG = Replace(sMSG, Match.Value, " ") 53 | Next 54 | 55 | REM - replace double/multiple spaces 56 | strRegEx = "([\s]{2,})" 57 | Set Matches = oLookup(strRegEx, sMSG, False) 58 | For Each Match In Matches 59 | sMSG = Replace(sMSG, Match.Value, " ") 60 | Next 61 | 62 | sMSGLen = len(sMSG) 63 | 64 | Set WshShell = CreateObject("WScript.Shell") 65 | rc = WshShell.run( "cmd.exe /c gammu-smsd-inject -c C:\gammu\bin\smsdrc TEXT " & SMSNumber & " -len " & sMSGLen & " -text " & Chr(34) & sMSG & Chr(34), 0, True ) 66 | Set WshShell = Nothing 67 | 68 | EventLog.Write( ":::::::::::::::: hMailServer automated SMS Forwarding Service ::::::::::::::::" ) 69 | EventLog.Write( ":Error : None" ) 70 | EventLog.Write( ":Sent To : " & SMSNumber ) 71 | EventLog.Write( ":Message Length: " & sMSGLen ) 72 | EventLog.Write( ":Message : " & sMSG ) 73 | EventLog.Write( ": :" ) 74 | End Function 75 | 76 | Sub GetTwoFactorInfo(ByVal s_Account, ByRef m_MobileNumber, ByRef m_LastLogon, ByRef m_LastLockTime) 77 | Dim oRecord, oConn : Set oConn = CreateObject("ADODB.Connection") 78 | oConn.Open "Driver={MariaDB ODBC 3.0 Driver}; Server=localhost; Database=hmailserver; User=hmailserver; Password=supersecretpassword;" 79 | 80 | If oConn.State <> 1 Then 81 | EventLog.Write( "Sub GetTwoFactorInfo - ERROR: Could not connect to database" ) 82 | m_MobileNumber = "0" 83 | m_LastLogon = "1969-12-31 23:59:59" 84 | m_LastLockTime = "1969-12-31 23:59:59" 85 | Exit Sub 86 | End If 87 | 88 | m_MobileNumber = "0" 89 | m_LastLogon = "1969-12-31 23:59:59" 90 | m_LastLockTime = "1969-12-31 23:59:59" 91 | 92 | Set oRecord = oConn.Execute("SELECT mobilenumber, lastlogontime, lastlocktime FROM hm_accounts_mobile WHERE account = '" & s_Account & "';") 93 | Do Until oRecord.EOF 94 | m_MobileNumber = oRecord("mobilenumber") 95 | m_LastLogon = oRecord("lastlogontime") 96 | m_LastLockTime = oRecord("lastlocktime") 97 | oRecord.MoveNext 98 | Loop 99 | oConn.Close 100 | Set oRecord = Nothing 101 | End Sub 102 | 103 | Function SetSwitchOn(sAccount) 104 | Dim strSQL, oDB : Set oDB = GetDatabaseObject 105 | strSQL = "UPDATE hm_accounts_mobile SET accountlock=1, lastlocktime=NOW() WHERE account = '" & sAccount & "';" 106 | Call oDB.ExecuteSQL(strSQL) 107 | Set oDB = Nothing 108 | End Function 109 | 110 | Function UpdateLastLogon(sAccount) 111 | Dim strSQL, oDB : Set oDB = GetDatabaseObject 112 | strSQL = "UPDATE hm_accounts_mobile SET lastlogontime = NOW() WHERE account = '" & sAccount & "';" 113 | Call oDB.ExecuteSQL(strSQL) 114 | Set oDB = Nothing 115 | End Function 116 | 117 | Sub OnClientLogon(oClient) 118 | 119 | Dim strRegEx, sMSG, SMSNumber 120 | If oClient.Authenticated Then 121 | 122 | REM - Include domains you want to apply inactivity based two factor auth 123 | strRegEx = "@domain1\.com|@domain\.com|@domain3\.com" 124 | If Lookup(strRegEx, oClient.Username) Then 125 | 126 | REM - Two Factor Authentication - get variables 127 | Dim m_MobileNumber, m_LastLogon, m_LastLockTime 128 | Call GetTwoFactorInfo(oClient.Username, m_MobileNumber, m_LastLogon, m_LastLockTime) 129 | 130 | REM - If account is a real person, then begin two factor check 131 | If m_MobileNumber <> "0" Then 132 | Dim a : a = Split( oClient.Username, "@" ) 133 | Dim AcctDomain : AcctDomain = Trim( CStr( a(1) ) ) 134 | Dim A_Domain : A_Domain = UCase(Left(AcctDomain, 1)) & Mid(AcctDomain, 2) 135 | Dim A_AcctCaps : A_AcctCaps = UCase(oClient.Username) 136 | 137 | REM - If last logon outside of interval, trip 2 factor 138 | REM - DateAdd can be one of the following: "yyyy" Year, "m" Month, "d" Day, "h" Hour, "n" Minute, "s" Second 139 | If (DateAdd("d", 7, m_LastLogon)) < Now() Then 140 | Call Disconnect(oClient.IPAddress) 141 | Call SetSwitchOn(oClient.Username) 142 | 143 | REM - Prevent redundant notifications 144 | If (DateAdd("n", 5, m_LastLockTime)) < Now() Then 145 | sMSG = "Message from " & A_Domain & " Mail Server: For security reasons and your safety, your account (" & oClient.Username & ") has been temporarily disabled. Reply UNLOCK to enable your account." 146 | Call SendSMS(m_MobileNumber, sMSG) 147 | End If 148 | Else 149 | REM - If last logon within interval, update logon time 150 | Call UpdateLastLogon(oClient.Username) 151 | End If 152 | End If 153 | End If 154 | 155 | Else 156 | ' 157 | ' Whatever you want to do on UNsuccessful logon 158 | ' 159 | End if 160 | End Sub 161 | -------------------------------------------------------------------------------- /www/account.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | exec("UPDATE hm_accounts_mobile SET mobilenumber=".$updatemobilenumber." WHERE account='".$account."';"); 11 | header("Location: ./account.php?account=".$account); 12 | } 13 | if (isset($_GET['updatelastlogontime'])){ 14 | $pdo->exec("UPDATE hm_accounts_mobile SET lastlogontime=NOW() WHERE account='".$account."';"); 15 | header("Location: ./account.php?account=".$account); 16 | } 17 | if (isset($_GET['updatemessagecount'])){ 18 | $pdo->exec("UPDATE hm_accounts_mobile SET messagecount=0 WHERE account='".$account."';"); 19 | header("Location: ./account.php?account=".$account); 20 | } 21 | if (isset($_GET['enableaccount'])){ 22 | enableAccount($account); 23 | $pdo->exec("UPDATE hm_accounts_mobile SET accountdisabled=0 WHERE account='".$account."';"); 24 | header("Location: ./account.php?account=".$account); 25 | } 26 | if (isset($_GET['disableaccount'])){ 27 | disableAccount($account); 28 | $pdo->exec("UPDATE hm_accounts_mobile SET accountdisabled=1 WHERE account='".$account."';"); 29 | header("Location: ./account.php?account=".$account); 30 | } 31 | if (isset($_GET['unlockaccount'])){ 32 | $pdo->exec("UPDATE hm_accounts_mobile SET accountlock=0, lastlogontime=NOW() WHERE account='".$account."';"); 33 | header("Location: ./account.php?account=".$account); 34 | } 35 | if (isset($_GET['lockaccount'])){ 36 | $pdo->exec("UPDATE hm_accounts_mobile SET accountlock=1 WHERE account='".$account."';"); 37 | header("Location: ./account.php?account=".$account); 38 | } 39 | 40 | echo "
"; 41 | echo "

"; 42 | echo "Account: ".$account.""; 43 | echo "

"; 44 | 45 | $sql = $pdo->prepare(" 46 | SELECT 47 | account, 48 | mobilenumber, 49 | lastlocktime, 50 | lastlogontime, 51 | lastmessagetime, 52 | messagecount, 53 | accountdisabled, 54 | accountlock, 55 | initpw 56 | FROM hm_accounts_mobile 57 | WHERE account = '".$account."'; 58 | "); 59 | $sql->execute(); 60 | echo ""; 61 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 62 | echo " 63 | 64 | 65 | 72 | "; 73 | echo " 74 | 75 | 76 | 78 | "; 79 | echo " 80 | 81 | 82 | 88 | "; 89 | echo " 90 | 91 | 92 | 93 | "; 95 | echo " 96 | 97 | 98 | 104 | "; 105 | if ($row['accountdisabled']==0){$disabledaccount="No";}else{$disabledaccount="Yes";} 106 | echo " 107 | 108 | 109 | 120 | "; 121 | if ($row['accountlock']==0){$lockedaccount="No";}else{$lockedaccount="Yes";} 122 | echo " 123 | 124 | 125 | 136 | "; 137 | if ($row['initpw']==0){$pwinit="No";}else{$pwinit="Yes";} 138 | echo " 139 | 140 | 141 | 142 | "; 144 | } 145 | echo "
Mobile Number:".displayMobileNumber($row['mobilenumber'])." 66 |
67 | 68 | 69 | 70 |
71 |
Last Lock:".$row['lastlocktime']." 77 |
Last Logon:".$row['lastlogontime']." 83 |
84 | 85 | 86 |
87 |
Last Message:".$row['lastmessagetime']." 94 |
Message Count:".$row['messagecount']." 99 |
100 | 101 | 102 |
103 |
Account Disabled:".$disabledaccount." 110 |
111 | 112 | 113 |
114 |
115 |
116 | 117 | 118 |
119 |
Account Locked:".$lockedaccount." 126 |
127 | 128 | 129 |
130 |
131 |
132 | 133 | 134 |
135 |
Password Change Initiated:".$pwinit." 143 |
"; 146 | 147 | echo "

"; 148 | 149 | echo ""; 150 | echo ""; 151 | echo ""; 152 | echo ""; 153 | echo "
Notes
Disable/Enable Account: Literally disables account - can no longer logon, send or receive mail. Same as \"Enabled\" checkbox in hMailServer Admin account > general page.

Lock/Unlock Account: Sets account lock switch to 0 or 1. With switch enabled (1), user gets disconnected at every logon attampt, preventing user from logging on or sending mail, but does not interfere with receiving mail.
"; 154 | 155 | echo "
"; 156 | 157 | ?> -------------------------------------------------------------------------------- /RunOnReceive/pwCommon.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | hMailServer SMS Password Changer 5 | 6 | .DESCRIPTION 7 | Support functions and data for SMS Password Changer 8 | 9 | .FUNCTIONALITY 10 | 11 | 12 | .PARAMETER ID 13 | Specifies the gammu database inbox ID number. Used for retrieval of sender number and message. 14 | 15 | .NOTES 16 | Fill User & MySQL variables below 17 | 18 | .EXAMPLE 19 | 20 | 21 | #> 22 | 23 | <### USER VARIABLES ###> 24 | $hMSAdminPass = "supersecretpassword" #<-- hMailServer Administrator Password 25 | $hMSLogFolder = "C:\Program Files (x86)\hMailServer\Logs" #<-- hMailServer Log Folder 26 | $MsgLimit = 100 #<-- Outgoing daily message limit per user 27 | $PWURL = "https://mydomain.tld/pw" #<-- URL of password changer website 28 | $WebMailURL = "https://webmail.mydomain.tld" #<-- URL of webmail 29 | $AdminNumber = "1234567890" #<-- Mobile number of system admin for notifications 30 | 31 | <### MYSQL VARIABLES ###> 32 | $SQLAdminUserName = 'hmailserver' 33 | $SQLAdminPassword = 'supersecretpassword' 34 | $SQLDatabase = 'hmailserver' 35 | $SQLHost = '127.0.0.1' 36 | $SQLPort = 3306 37 | $SQLSSL = 'none' 38 | 39 | <# Database Function #> 40 | Function MySQLQuery($Query) { 41 | $Today = (Get-Date).ToString("yyyyMMdd") 42 | $DBErrorLog = "$PSScriptRoot\$Today-Limit-Outgoing-DBError.log" 43 | $ConnectionString = "server=" + $SQLHost + ";port=" + $SQLPort + ";uid=" + $SQLAdminUserName + ";pwd=" + $SQLAdminPassword + ";database=" + $SQLDatabase + ";SslMode=" + $SQLSSL + ";" 44 | Try { 45 | [void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data") 46 | $Connection = New-Object MySql.Data.MySqlClient.MySqlConnection 47 | $Connection.ConnectionString = $ConnectionString 48 | $Connection.Open() 49 | $Command = New-Object MySql.Data.MySqlClient.MySqlCommand($Query, $Connection) 50 | $DataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($Command) 51 | $DataSet = New-Object System.Data.DataSet 52 | $RecordCount = $dataAdapter.Fill($DataSet, "data") 53 | $DataSet.Tables[0] 54 | } 55 | Catch { 56 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to run query : $query `n$Error[0]" | Out-File $DBErrorLog -append 57 | } 58 | Finally { 59 | $Connection.Close() 60 | } 61 | } 62 | 63 | <# Send SMS Message #> 64 | Function SendSMS($Num, $Msg) { 65 | [int]$len = ([convert]::ToInt32(($Msg.length), 10)) 66 | & cmd.exe /c gammu-smsd-inject -c C:\gammu\bin\smsdrc TEXT $Num -len $len -text $Msg 67 | } 68 | 69 | <# Password Validation #> 70 | Function TestPassword ([string]$password){ 71 | [bool]($password| 72 | where-object {$_.length -gt 11} | 73 | where-object {$_ -match '[a-z]'}| 74 | where-object {$_ -match '[A-Z]'}| 75 | where-object {$_ -match '[0-9]'}| 76 | where-object {$_ -match '[!#$%&*+,-.:=?^_~]'}| 77 | where-object {$_ -notmatch '[\s\n]'}| 78 | where-object {$_ -match '[^a-zA-Z0-9]'} 79 | ) 80 | } 81 | 82 | <# Authenticate hMailServer COM #> 83 | $hMS = (New-Object -COMObject hMailServer.Application) 84 | $hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null 85 | 86 | <# Change Password Function #> 87 | Function Change-Password ($Email, $Password){ 88 | $Domain = $Email.split('@')[1] 89 | $hMSAccount = ($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Email) 90 | $hMSAccount.Password = $Password 91 | $hMSAccount.Save() 92 | } 93 | 94 | <# Disable Account Function #> 95 | Function DisableAccount($Account){ 96 | <# Disable account #> 97 | $Domain = ($Account).Split("@")[1] 98 | $hMSAccountStatus = ($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Account) 99 | $hMSAccountStatus.Active = $False 100 | $hMSAccountStatus.Save() 101 | 102 | <# Update accountdisabled in database #> 103 | $UpdateQuery = "UPDATE hm_accounts_mobile SET accountdisabled = 1 WHERE account = '$Account';" 104 | MySQLQuery $UpdateQuery 105 | } 106 | 107 | <# Enable Account Function #> 108 | Function EnableAccount($Account){ 109 | <# Enable account #> 110 | $Domain = ($Account).Split("@")[1] 111 | $hMSAccountStatus = ($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Account) 112 | $hMSAccountStatus.Active = $True 113 | $hMSAccountStatus.Save() 114 | 115 | <# Update accountdisabled in database #> 116 | $UpdateQuery = "UPDATE hm_accounts_mobile SET accountdisabled = 0 WHERE account = '$Account';" 117 | MySQLQuery $UpdateQuery 118 | } 119 | 120 | <# Is Account Enabled Function #> 121 | Function IsAccountEnabled($Account){ 122 | $Status = $False 123 | $Domain = ($Account).Split("@")[1] 124 | $hMSAccountStatus = ($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Account) 125 | If ($hMSAccountStatus.Active -eq $True){ 126 | $Status = $True 127 | } 128 | Return $Status 129 | } 130 | 131 | <# Random Password Generator Function #> 132 | Function MakeUp-String([Int]$Size = 12, [Char[]]$CharSets = "ULNS", [Char[]]$Exclude) { 133 | $Chars = @(); $TokenSet = @() 134 | If (!$TokenSets) {$Global:TokenSets = @{ 135 | U = [Char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ' #Upper case 136 | L = [Char[]]'abcdefghijklmnopqrstuvwxyz' #Lower case 137 | N = [Char[]]'0123456789' #Numerals 138 | S = [Char[]]'!#$%&*+,-.:=?^_~' #Symbols 139 | }} 140 | $CharSets | ForEach { 141 | $Tokens = $TokenSets."$_" | ForEach {If ($Exclude -cNotContains $_) {$_}} 142 | If ($Tokens) { 143 | $TokensSet += $Tokens 144 | If ($_ -cle [Char]"Z") {$Chars += $Tokens | Get-Random} #Character sets defined in upper case are mandatory 145 | } 146 | } 147 | While ($Chars.Count -lt $Size) {$Chars += $TokensSet | Get-Random} 148 | ($Chars | Sort-Object {Get-Random}) -Join "" #Mix the (mandatory) characters and output string 149 | }; Set-Alias Create-Password MakeUp-String -Description "Generate a random string (password)" 150 | 151 | <# Get account from mobile number #> 152 | Function GetAccount($Num){ 153 | $Email = "" 154 | 155 | $Query = "SELECT COUNT(account) AS countaccount FROM hm_accounts_mobile WHERE mobilenumber = '$Num';" 156 | MySQLQuery $Query | ForEach { 157 | $CountAccount = $_.countaccount 158 | } 159 | 160 | <# If count = 1, use that address #> 161 | If ($CountAccount -eq 1){ 162 | <# Match email to number #> 163 | $Query = "SELECT account FROM hm_accounts_mobile WHERE mobilenumber = '$Num';" 164 | MySQLQuery $Query | ForEach { 165 | $Email = $_.account 166 | } 167 | } Else { 168 | $Query = "SELECT account FROM hm_accounts_mobile WHERE mobilenumber = '$Num' AND initpw = 1" 169 | MySQLQuery $Query | ForEach { 170 | $Email = $_.account 171 | } 172 | } 173 | 174 | Return $Email 175 | } -------------------------------------------------------------------------------- /RunOnReceive/pwChange.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | hMailServer SMS Password Changer 5 | 6 | .DESCRIPTION 7 | Change your hMailServer account password via SMS 8 | 9 | .FUNCTIONALITY 10 | 1) Initiates password change by SMS command 11 | 2) Option to create personalized password or have a random one created for you 12 | 3) Enforces strong passwords 13 | 4) Detailed instructions given at every event 14 | 15 | .PARAMETER rorNum 16 | Specifies the SMS mobile number. Used to validate account and for returning confirmation. 17 | 18 | .PARAMETER rorMsg 19 | Specifies the SMS message. 20 | 21 | .NOTES 22 | Documentation available at PW change website. 23 | 24 | .EXAMPLE 25 | Initiate password change via SMS: 26 | pw change 27 | 28 | Re-Initiation of password change via SMS when multiple accounts are associated with a single mobile number: 29 | pw change 30 | 31 | Request random password assigned via SMS: 32 | pw random 33 | 34 | Request personalized password via SMS: 35 | pw mine 36 | 37 | Dictate new password via SMS: 38 | pw new 39 | #> 40 | 41 | Param( 42 | [ValidatePattern('^\+\d{11}$|^\d{10}$')] 43 | [string]$rorNum, 44 | [ValidatePattern('pw[\s](change|mine|new|random)')] 45 | [string]$rorMsg 46 | ) 47 | 48 | <# Include required files #> 49 | Try { 50 | .("$PSScriptRoot\pwCommon.ps1") 51 | } 52 | Catch { 53 | Write-Output "$((get-date).ToString(`"yy/MM/dd HH:mm:ss.ff`")) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 54 | } 55 | 56 | <# Set script variables from parameters #> 57 | [regex]$RegExNum = "[0-9]{10}$" 58 | $Num = ($RegExNum.Matches($rorNum)).Value 59 | 60 | <# RegEx #> 61 | $RegexEmail = "([A-Za-z0-9!.#$%&'*+\/=?^_`{|}~-]+@([A-Za-z0-9-]{2,63}\.){1,10}[A-Za-z0-9-]{2,12})" 62 | [regex]$RegexMultiMatch = "([pP][wW]\s[cC][hH][aA][nN][gG][eE]\s)$RegexEmail" 63 | 64 | <# If reply contains "PW CHANGE , use the email address in the password changer #> 65 | If ($rorMsg -match $RegexMultiMatch){ 66 | $PWChangeMsg = ($rorMsg).Trim() 67 | $EmailFromMsg = $PWChangeMsg.Split(" ")[2] 68 | 69 | <# Validate email address #> 70 | $Query = "SELECT COUNT(account) AS countaccount FROM hm_accounts_mobile WHERE account = '$EmailFromMsg';" 71 | MySQLQuery $Query | ForEach { 72 | $CountAccount = $_.countaccount 73 | Write-Host $CountAccount 74 | } 75 | 76 | <# If only one address matches, set email and send initial message #> 77 | If ($CountAccount -eq 1){ 78 | 79 | <# Set email #> 80 | $Email = $EmailFromMsg 81 | $Query = "UPDATE hm_accounts_mobile SET initpw = 1 WHERE account = '$Email';" 82 | MySQLQuery $Query 83 | 84 | <# Send first set of instructions #> 85 | $Msg = "You have requested a new password for $email. Refer to $PWURL for instructions. You can make your own password or I can create a random one. Reply PW MINE to create your own or PW RANDOM if you want me to create it for you." 86 | SendSMS $Num $Msg 87 | 88 | } Else { 89 | <# If 0 or multiple addresses found, kick it back #> 90 | $Msg = "The email address you provided cannot be found. Please check the spelling and try again. Reply: PW CHANGE email@domain.com" 91 | } 92 | 93 | } ElseIf ($rorMsg -match "([pP][wW]\s[cC][hH][aA][nN][gG][eE])(\s+)?"){ 94 | 95 | <# Count matches to email from number #> 96 | $Query = "SELECT COUNT(account) AS countaccount FROM hm_accounts_mobile WHERE mobilenumber = '$Num';" 97 | MySQLQuery $Query | ForEach { 98 | $CountAccount = $_.countaccount 99 | } 100 | 101 | <# If count = 1, use that address #> 102 | If ($CountAccount -eq 1){ 103 | 104 | <# Match email to number #> 105 | $Query = "SELECT account FROM hm_accounts_mobile WHERE mobilenumber = '$Num';" 106 | MySQLQuery $Query | ForEach { 107 | $Email = $_.account 108 | 109 | <# Send first set of instructions #> 110 | $Msg = "You have requested a new password for $email. Refer to $PWURL for instructions. You can make your own password or I can create a random one. Reply PW MINE to create your own or PW RANDOM if you want me to create it for you." 111 | SendSMS $Num $Msg 112 | } 113 | 114 | } Else { 115 | <# If count > 1, send message with instructions #> 116 | $Msg = "You have multiple accounts associated with your mobile number. Reply with the account you want to change: PW CHANGE username@domain.com" 117 | SendSMS $Num $Msg 118 | } 119 | } 120 | 121 | <# If REPLY = MINE #> 122 | If ($rorMsg -match "([pP][wW]\s[mM][iI][nN][eE])(\s+)?"){ 123 | 124 | <# Send instructions for personalized password creation #> 125 | $Msg = "You have chosen to create your own password. Your password must be a minimum of 12 characters and contain at least one of the following characters !#$%&*+,-.:=?^_~ as well as at least one capital letter, one lowercase letter and one numeric digit. Reply PW NEW followed by your chosen password." 126 | SendSMS $Num $Msg 127 | } 128 | 129 | <# If REPLY = NEW #> 130 | If ($rorMsg -match "([pP][wW]\s[nN][eE][wW])(\s+)?"){ 131 | $Email = GetAccount $Num 132 | $Domain = ($Email).Split("@")[1] 133 | 134 | <# Trim incoming message to leave only password as string #> 135 | $Password = $rorMsg -replace ('pw\snew\s','') 136 | $Password = $Password -replace ('\s$','') 137 | 138 | <# Test personalized password for validation and deny if validation fails #> 139 | If(-not(TestPassword $Password)){ 140 | $Msg = "Your password ($Password) did not pass validation. See $PWURL for reference. Your password must be a minimum of 12 characters and contain at least one of the following characters !#$%&*+,-.:=?^_~ as well as at least one capital letter, one lowercase letter and one number. Reply PW NEW followed by your chosen password." 141 | SendSMS $Num $Msg 142 | } 143 | 144 | <# If validation successful, change password and send confirmation message #> 145 | Else { 146 | 147 | <# First, change password #> 148 | Change-Password $Email $Password 149 | 150 | <# If password change successful, send confirmation message #> 151 | If ($((($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Email)).ValidatePassword($Password)) -eq $True){ 152 | $Msg = "Your new password ($Password) for $Email was accepted. Please go to $WebMailURL to log into your email." 153 | SendSMS $Num $Msg 154 | 155 | <# If account disabled, enable it now that password has changed #> 156 | If (-not(IsAccountEnabled $Email)){ 157 | 158 | <# Enable account #> 159 | EnableAccount $Email 160 | 161 | <# Send admin confirmation message #> 162 | $AdminMsg = "User $Account successfully changed password and unlocked account." 163 | SendSMS $AdminNumber $AdminMsg 164 | 165 | <# Reset message count so it doesn't repeatedly trip account disable #> 166 | $Query = "UPDATE hm_accounts_mobile SET messagecount = 0 WHERE account = '$Email';" 167 | MySQLQuery $Query 168 | } 169 | <# Reset initpw so we don't confuse the correct account/mobilenumber combination next time we need it #> 170 | $Query = "UPDATE hm_accounts_mobile SET initpw = 0 WHERE account = '$Email';" 171 | MySQLQuery $Query 172 | } 173 | 174 | Else { 175 | <# On error, send error message #> 176 | $Msg = "Something went wrong. Please contact the administrator." 177 | SendSMS $Num $Msg 178 | 179 | <# On error, notify admin #> 180 | $AdminMsg = "ERROR - Something went wrong with $Account password change." 181 | SendSMS $AdminNumber $AdminMsg 182 | Exit 183 | } 184 | } 185 | } 186 | 187 | <# If REPLY = RANDOM #> 188 | If ($rorMsg -match "([pP][wW]\s[rR][aA][nN][dD][oO][mM])(\s+)?"){ 189 | $Email = GetAccount $Num 190 | $Domain = ($Email).Split("@")[1] 191 | 192 | <# Create random 12 char password and use it to change password #> 193 | $Password = Create-Password 12 ULNS "OLIoli01" 194 | Change-Password $Email $Password 195 | 196 | <# If password change successful, send confirmation message #> 197 | If ($((($hMS.Domains.ItemByName($Domain)).Accounts.ItemByAddress($Email)).ValidatePassword($Password)) -eq $True){ 198 | $Msg = "Your new password is $password for the account $Email. Please go to $WebMailURL to log into your email." 199 | SendSMS $Num $Msg 200 | 201 | <# If account disabled, enable it now that password has changed #> 202 | If (-not(IsAccountEnabled $Email)){ 203 | 204 | <# Enable account #> 205 | EnableAccount $Email 206 | 207 | <# Send admin confirmation message #> 208 | $AdminMsg = "User $Account successfully changed password and unlocked account." 209 | SendSMS $AdminNumber $AdminMsg 210 | 211 | <# Reset message count so it doesn't repeatedly trip account disable #> 212 | $Query = "UPDATE hm_accounts_mobile SET messagecount = 0 WHERE account = '$Email';" 213 | MySQLQuery $Query 214 | } 215 | <# Reset initpw so we don't confuse the correct account/mobilenumber combination next time we need it #> 216 | $Query = "UPDATE hm_accounts_mobile SET initpw = 0 WHERE account = '$Email';" 217 | MySQLQuery $Query 218 | } 219 | 220 | Else { 221 | <# On error, send error message #> 222 | $Msg = "Something went wrong. Please contact the administrator." 223 | SendSMS $Num $Msg 224 | 225 | <# On error, notify admin #> 226 | $AdminMsg = "ERROR - Something went wrong with $Account password change." 227 | SendSMS $AdminNumber $AdminMsg 228 | Exit 229 | } 230 | } --------------------------------------------------------------------------------