├── .gitignore ├── index.php ├── m.php ├── img ├── bg.gif ├── asc.gif ├── desc.gif └── logo.png ├── favicon.ico ├── setup ├── blacklistmonitor-apache.conf ├── blacklistmonitor-ubuntu-upstart.conf ├── blacklistmonitor-centos-systemd.conf ├── blacklistmonitor.cron ├── blacklistmonitor.cfg └── blacklistmonitor.sql ├── footer.inc.php ├── accountSubnav.inc.php ├── classes ├── _MeasurePerformance.class.php ├── _Logging.class.php ├── Twitter.class.php ├── Setup.class.php ├── _MySQL.class.php ├── _FileCache.class.php ├── _IpAddresses.class.php ├── TwitterAPIExchange.php ├── Utilities.class.php └── class.smtp.php ├── LICENSE.txt ├── login.php ├── header.inc.php ├── service ├── cron.php ├── monitorJob.php ├── blacklistmonitor.php └── userJob.php ├── css └── site.css ├── monitorGroup.php ├── hostHistory.php ├── api.php ├── README.md ├── blockLists.php ├── editHostGroup.php ├── apiDocumentation.php ├── hosts.php ├── account.php └── js └── jquery.tablesorter.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelynn2/blacklistmonitor/HEAD/img/bg.gif -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelynn2/blacklistmonitor/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelynn2/blacklistmonitor/HEAD/img/asc.gif -------------------------------------------------------------------------------- /img/desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelynn2/blacklistmonitor/HEAD/img/desc.gif -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelynn2/blacklistmonitor/HEAD/img/logo.png -------------------------------------------------------------------------------- /setup/blacklistmonitor-apache.conf: -------------------------------------------------------------------------------- 1 | ScriptAlias /blacklistmonitor /var/www/html/blacklistmonitor 2 | -------------------------------------------------------------------------------- /footer.inc.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /setup/blacklistmonitor-ubuntu-upstart.conf: -------------------------------------------------------------------------------- 1 | description "BlackListMonitor" 2 | 3 | start on runlevel [2345] 4 | stop on runlevel [!2345] 5 | respawn 6 | respawn limit 20 10 7 | 8 | limit nofile 32768 32768 9 | 10 | script 11 | set -e 12 | exec su -s /bin/sh -c 'exec "$0" "$@"' root -- /usr/bin/php /var/www/html/blacklistmonitor/service/blacklistmonitor.php >>/var/log/blacklistmonitor.log 2>&1 13 | end script 14 | -------------------------------------------------------------------------------- /accountSubnav.inc.php: -------------------------------------------------------------------------------- 1 |
2 | 8 |
-------------------------------------------------------------------------------- /setup/blacklistmonitor-centos-systemd.conf: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Blacklist monitor 3 | 4 | [Service] 5 | Type=simple 6 | User=root 7 | Group=root 8 | ExecStart=/usr/bin/php /var/www/html/blacklistmonitor/service/blacklistmonitor.php >>/var/log/blacklistmonitor.log 2>&1 9 | 10 | # Give a reasonable amount of time for the server to start up/shut down 11 | TimeoutSec=300 12 | 13 | # how to setup for centos 14 | # cp /var/www/html/blacklistmonitor/setup/blacklistmonitor-centos-systemd.conf /etc/systemd/system/blacklistmonitor.service 15 | # systemctl restart blacklistmonitor.service 16 | # systemctl stop blacklistmonitor.service 17 | # systemctl start blacklistmonitor.service 18 | -------------------------------------------------------------------------------- /classes/_MeasurePerformance.class.php: -------------------------------------------------------------------------------- 1 | work(1); 8 | $m->work(1); 9 | echo $m->totalAvgPerformance; 10 | */ 11 | public $startTime; 12 | public $counter = 0; 13 | public $avgPerformance = ''; 14 | public $runTime = 0; 15 | 16 | public function __construct(){ 17 | $this->startTime = microtime(true); 18 | } 19 | 20 | public function work($counterAddFloat = 1){ 21 | $this->counter = $this->counter + (float)$counterAddFloat; 22 | } 23 | public function endWork(){ 24 | $this->runTime = microtime(true) - $this->startTime; 25 | $this->avgPerformance = number_format(($this->counter/$this->runTime), 2, '.', ''); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /setup/blacklistmonitor.cron: -------------------------------------------------------------------------------- 1 | 0 9 * * 1 php /var/www/html/blacklistmonitor/service/cron.php -r weekly >>/var/log/blacklistmonitor.log 2>&1 2 | @daily php /var/www/html/blacklistmonitor/service/cron.php -r daily >>/var/log/blacklistmonitor.log 2>&1 3 | 0 */8 * * * php /var/www/html/blacklistmonitor/service/cron.php -r 8hour >>/var/log/blacklistmonitor.log 2>&1 4 | 0 */2 * * * php /var/www/html/blacklistmonitor/service/cron.php -r 2hour >>/var/log/blacklistmonitor.log 2>&1 5 | @hourly php /var/www/html/blacklistmonitor/service/cron.php -r 1hour >>/var/log/blacklistmonitor.log 2>&1 6 | 7 | 0 0 * * * php /var/www/html/blacklistmonitor/service/cron.php -r blockListStats >>/var/log/blacklistmonitor.log 2>&1 8 | @daily php /var/www/html/blacklistmonitor/service/cron.php -r deleteOld >>/var/log/blacklistmonitor.log 2>&1 9 | -------------------------------------------------------------------------------- /classes/_Logging.class.php: -------------------------------------------------------------------------------- 1 | settings = array( 14 | 'oauth_access_token' => Setup::$settings['twitter_oauth_access_token'], 15 | 'oauth_access_token_secret' => Setup::$settings['twitter_access_token_secret'], 16 | 'consumer_key' => Setup::$settings['twitter_consumer_key'], 17 | 'consumer_secret' => Setup::$settings['twitter_consumer_secret'] 18 | ); 19 | $this->twitter = new TwitterAPIExchange($this->settings); 20 | } 21 | 22 | public function follow($user){ 23 | $postfields = array( 24 | 'screen_name'=>$user, 25 | 'follow'=>'true' 26 | ); 27 | return $this->twitter->buildOauth('https://api.twitter.com/1.1/friendships/create.json', 'POST') 28 | ->setPostfields($postfields) 29 | ->performRequest(); 30 | } 31 | 32 | public function message($user, $message){ 33 | $message = trim($message); 34 | $postfields = array( 35 | 'screen_name'=>$user, 36 | 'text'=>$message 37 | ); 38 | return $this->twitter->buildOauth('https://api.twitter.com/1.1/direct_messages/new.json', 'POST') 39 | ->setPostfields($postfields) 40 | ->performRequest(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /classes/Setup.class.php: -------------------------------------------------------------------------------- 1 | window.location='login.php';"); 17 | exit(); 18 | } 19 | 20 | if(Utilities::isLoggedIn()!==false){ 21 | header('Location: account.php'); 22 | exit(); 23 | } 24 | 25 | if(isset($_POST["submit"])){ 26 | $id = Utilities::validateLogin($username, $passwd); 27 | if($id != 0){ 28 | $_SESSION['id'] = $id; 29 | ?> 30 | 31 | '; 35 | } 36 | }?> 37 | 38 | $message"); 40 | ?> 41 | 42 |
43 |

Please sign in

44 | 45 | 46 | 47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /header.inc.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | <?php if(isset($titlePreFix)) echo("$titlePreFix | ");?>Open Source Blacklist Monitoring 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
23 |
24 | 39 | logo
40 |
41 | -------------------------------------------------------------------------------- /service/cron.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | connect(Setup::$connectionArray); 19 | 20 | if($options['r']=='blockListStats'){ 21 | $mysql->runQuery("update blockLists set blocksYesterday = blocksToday, cleanYesterday = cleanToday; "); 22 | $mysql->runQuery("update blockLists set blocksToday = 0, cleanToday = 0; "); 23 | _Logging::appLog("block list stats updated"); 24 | } 25 | if($options['r']=='weekly'){ 26 | $mysql->runQuery("update users set beenChecked = 0 where checkFrequency = 'weekly';"); 27 | _Logging::appLog("weekly reset"); 28 | } 29 | if($options['r']=='daily'){ 30 | $mysql->runQuery("update users set beenChecked = 0 where checkFrequency = 'daily';"); 31 | _Logging::appLog("daily reset"); 32 | } 33 | if($options['r']=='8hour'){ 34 | $mysql->runQuery("update users set beenChecked = 0 where checkFrequency = '8hour';"); 35 | _Logging::appLog("8 hour reset"); 36 | } 37 | if($options['r']=='2hour'){ 38 | $mysql->runQuery("update users set beenChecked = 0 where checkFrequency = '2hour';"); 39 | _Logging::appLog("2 hour reset"); 40 | } 41 | if($options['r']=='1hour'){ 42 | $mysql->runQuery("update users set beenChecked = 0 where checkFrequency = '1hour';"); 43 | _Logging::appLog("1 hour reset"); 44 | } 45 | if($options['r']=='deleteOld'){ 46 | //clear orphan status 47 | $mysql->runQuery(" 48 | delete mh 49 | from monitorHistory mh 50 | left join monitors m on m.ipDomain = mh.ipDomain 51 | where m.ipDomain IS NULL 52 | "); 53 | 54 | $days = isset(Setup::$settings['history_keep_days']) ? (int)Setup::$settings['history_keep_days'] : 0; 55 | 56 | if($days > 0){ 57 | $mysql->runQuery(" 58 | delete from monitorHistory 59 | where monitorTime <= DATE_SUB(NOW(), INTERVAL $days day) 60 | "); 61 | 62 | _Logging::appLog("old data deleted"); 63 | } 64 | 65 | } 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /classes/_MySQL.class.php: -------------------------------------------------------------------------------- 1 | 4 | * How to use this class for updating queries: 5 | * $mysql = new _MySQL(); 6 | * $mysql->connect($connectionArray); 7 | * $rs = $mysql->runQuery($sqlquery); 8 | * $mysql->close(); 9 | * 10 | */ 11 | 12 | class _MySQL { 13 | 14 | public $affectedRows = 0; 15 | public $identity = 0; 16 | public $mysqlCon = null; 17 | public $connectionArray = null; 18 | 19 | /** 20 | * connect() - takes a connection array - host,user,pass,database - has a default but wont work without database 21 | **/ 22 | public function connect($connectionArray) { 23 | // [tammytattoo] Added support for port specifiers 24 | $hostParts = explode(':', $connectionArray[0]); 25 | if (count($hostParts)==2) { 26 | $connectionArray[0] = $hostParts[0]; 27 | $connectionArray[4] = $hostParts[1]; 28 | } else { 29 | $connectionArray[4] = 3306; 30 | } 31 | $this->connectionArray = $connectionArray; 32 | $this->close(); 33 | $this->mysqlCon = mysqli_connect( 34 | $connectionArray[0], 35 | $connectionArray[1], 36 | $connectionArray[2], 37 | $connectionArray[3], 38 | $connectionArray[4] 39 | ); 40 | 41 | // If no servers are responding, throw an exception. 42 | if ($this->mysqlCon===false) { 43 | throw new Exception( 44 | 'Unable to connect to any db servers - last error: '. 45 | mysqli_error()); 46 | } 47 | return $this->mysqlCon; 48 | } 49 | 50 | public function runQuery($query) { 51 | if (!mysqli_ping($this->mysqlCon)) { 52 | $this->connect($this->connectionArray); 53 | } 54 | $result = @mysqli_query($this->mysqlCon, $query); 55 | if ($result===false) { 56 | throw new Exception("Database query failed: $query\n\n".mysqli_error($this->mysqlCon)); 57 | } 58 | if ( 59 | (stripos($query, 'INSERT')!==false) || 60 | (stripos($query, 'UPDATE')!==false) || 61 | (stripos($query, 'DELETE')!==false) 62 | ) { 63 | $this->affectedRows = mysqli_affected_rows($this->mysqlCon); 64 | } 65 | if (stripos($query, 'INSERT')!==false) $this->identity = mysqli_insert_id($this->mysqlCon); 66 | return $result; 67 | } 68 | 69 | public function runQueryReturnVar($query) { 70 | $result = false; 71 | $rs = $this->runQuery($query." limit 1;"); 72 | while ($row = mysqli_fetch_array($rs)) $result = $row[0]; 73 | mysqli_free_result($rs); 74 | return $result; 75 | } 76 | 77 | public function escape($var) { 78 | return mysqli_real_escape_string($this->mysqlCon, $var); 79 | } 80 | 81 | public function close() { 82 | if ($this->mysqlCon!=null) { 83 | mysqli_close($this->mysqlCon); 84 | $this->mysqlCon = null; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 12px; 3 | padding-bottom: 12px; 4 | } 5 | 6 | .header,.marketing,.footer { 7 | padding-bottom: 10px; 8 | } 9 | 10 | .header h3 { 11 | margin-top: 0; 12 | margin-bottom: 0; 13 | line-height: 20px; 14 | padding-bottom: 12px; 15 | } 16 | 17 | .footer { 18 | font-size: 10px; 19 | padding-top: 10px; 20 | color: #777; 21 | } 22 | 23 | /* 24 | @media (min-width: 768px) { 25 | .container { 26 | max-width: 840px; 27 | } 28 | }*/ 29 | .container-narrow > hr { 30 | margin: 30px 0; 31 | } 32 | 33 | .jumbotron { 34 | margin: 5px 0; 35 | text-align: center; 36 | background-color:#ffffff; 37 | } 38 | .jumbotron h1 { 39 | font-size: 72px; 40 | font-weight: bold; 41 | line-height: 1; 42 | } 43 | .jumbotron .btn { 44 | font-size: 21px; 45 | padding: 14px 24px; 46 | } 47 | 48 | .marketing { 49 | margin: 40px 0; 50 | } 51 | .marketing p + h4 { 52 | margin-top: 28px; 53 | } 54 | /* 55 | @media screen and (min-width: 768px) { 56 | .header,.marketing,.footer { 57 | padding-left: 0; 58 | padding-right: 0; 59 | } 60 | .header { 61 | margin-bottom: 5px; 62 | } 63 | .jumbotron { 64 | border-bottom: 0; 65 | } 66 | }*/ 67 | #helpInfo { 68 | margin-bottom: 15px; 69 | } 70 | #accountSubnav { 71 | margin-bottom: 15px; 72 | } 73 | .form-signin { 74 | max-width: 330px; 75 | padding: 15px; 76 | margin: 0 auto; 77 | } 78 | .form-signin .form-signin-heading, 79 | .form-signin .checkbox { 80 | margin-bottom: 10px; 81 | } 82 | .form-signin .checkbox { 83 | font-weight: normal; 84 | } 85 | .form-signin .form-control { 86 | position: relative; 87 | font-size: 16px; 88 | height: auto; 89 | padding: 10px; 90 | -webkit-box-sizing: border-box; 91 | -moz-box-sizing: border-box; 92 | box-sizing: border-box; 93 | } 94 | .form-signin .form-control:focus { 95 | z-index: 2; 96 | } 97 | .form-signin input[type="text"] { 98 | margin-bottom: -1px; 99 | border-bottom-left-radius: 0; 100 | border-bottom-right-radius: 0; 101 | } 102 | .form-signin input[type="password"] { 103 | margin-bottom: 10px; 104 | border-top-left-radius: 0; 105 | border-top-right-radius: 0; 106 | } 107 | h4{ 108 | font-size: 1.1em; 109 | font-weight: bold; 110 | color: #000000; 111 | } 112 | .message { 113 | color: #FF0000; 114 | } 115 | table.tablesorter thead tr .header { 116 | background-image: url(../img/bg.gif); 117 | background-repeat: no-repeat; 118 | background-position: center right; 119 | cursor: pointer; 120 | } 121 | table.tablesorter thead tr .headerSortUp { 122 | background-image: url(../img/asc.gif); 123 | } 124 | table.tablesorter thead tr .headerSortDown { 125 | background-image: url(../img/desc.gif); 126 | } 127 | -------------------------------------------------------------------------------- /monitorGroup.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 18 | $sql = " 19 | select g.*, 20 | (select count(*) from monitors where g.id = monitorGroupId) as hostCount, 21 | (select count(*) from monitors where isBlocked = 1 and g.id = monitorGroupId) as hostCountError, 22 | (select count(*) from monitors where lastStatusChanged = 1 and isBlocked = 1 and g.id = monitorGroupId) as hostRecentBlock 23 | from monitorGroup g 24 | order by g.groupName 25 | "; 26 | $rs = $mysql->runQuery($sql); 27 | 28 | include('header.inc.php'); 29 | include('accountSubnav.inc.php'); 30 | 31 | ?> 32 | 33 | 34 | 35 | 40 | 41 |
42 | 46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | '); 62 | echo(''); 63 | echo(''); 64 | echo(''); 65 | echo(''); 66 | echo(''); 67 | echo(''); 68 | } 69 | $mysql->close(); 70 | ?> 71 | 72 |
Monitor GroupTotal HostsTotal BlocksLast Change New Blocks% Blocked
  '.$row['groupName'].'
'.number_format($row['hostCount'],0).''.number_format($row['hostCountError'],0).''.number_format($row['hostRecentBlock'],0).''.number_format((($row['hostCount']>0) ? ($row['hostCountError']/$row['hostCount']*100) : 0),1).'
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /service/monitorJob.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | connect(Setup::$connectionArray); 26 | 27 | $rs = $mysql->runQuery(" 28 | select * 29 | from monitors 30 | where ipDomain = '".$mysql->escape($options['h'])."'"); 31 | while($row = mysqli_fetch_array($rs)) { 32 | $monitor = $row; 33 | } 34 | 35 | // get blacklists 36 | Utilities::setBlockLists(); 37 | 38 | if( (empty(Utilities::$domainBlacklists)===true) && (empty(Utilities::$ipBlacklists)===true) ){ 39 | _Logging::appLog("no blacklists configured"); 40 | exit(); 41 | } 42 | 43 | //update monitor 44 | $result = serialize(Utilities::checkBlacklists($monitor['ipDomain'])); 45 | $isBlocked = Utilities::$isBlocked; 46 | $rdns = Utilities::lookupHostDNS($monitor['ipDomain']); 47 | $ctime = date('Y-m-d H:i:s'); 48 | $mysql->runQuery(" 49 | update monitors 50 | set 51 | lastStatusChanged = 0, 52 | rDNS = '".$mysql->escape($rdns)."', 53 | isBlocked = $isBlocked, 54 | lastUpdate = '$ctime', 55 | status = '".$mysql->escape($result)."' 56 | where ipDomain = '".$mysql->escape($monitor['ipDomain'])."' 57 | "); 58 | 59 | 60 | 61 | // status change on this host 62 | if(strcasecmp($result, $monitor['status']) != 0){ 63 | //update current status 64 | $mysql->runQuery(" 65 | update monitors 66 | set 67 | lastStatusChanged = 1, 68 | lastStatusChangeTime = '".date('Y-m-d H:i:s')."' 69 | where ipDomain = '".$mysql->escape($monitor['ipDomain'])."' 70 | "); 71 | 72 | //log history 73 | $mysql->runQuery(" 74 | insert into monitorHistory 75 | (monitorTime, isBlocked, ipDomain, rDNS, status) 76 | values( 77 | '".date('Y-m-d H:i:s')."', 78 | $isBlocked, 79 | '".$mysql->escape($monitor['ipDomain'])."', 80 | '".$mysql->escape($rdns)."', 81 | '".$mysql->escape($result)."')"); 82 | 83 | //make api callback 84 | $user = Utilities::getAccount(); 85 | if($user['apiCallbackURL']!=''){ 86 | Utilities::makeAPICallback($user['apiCallbackURL'], 87 | $monitor['ipDomain'], 88 | $isBlocked, 89 | $rdns, 90 | $result 91 | ); 92 | _Logging::appLog("api callback made: {$user['apiCallbackURL']}"); 93 | } 94 | } 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /hostHistory.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 18 | 19 | $daysOfHistory = Setup::$settings['history_keep_days']; 20 | $cutoffDate = date('Y-m-d', strtotime("-$daysOfHistory days")); 21 | 22 | $sql = " 23 | select isBlocked,monitorTime,rDNS,status 24 | from monitorHistory 25 | where ipDomain = '".$mysql->escape($host)."' 26 | and monitorTime >= '".$mysql->escape($cutoffDate)."' 27 | order by monitorTime desc 28 | "; 29 | $rs = $mysql->runQuery($sql); 30 | ?> 31 | 32 | 33 | 34 | 35 | 41 | 42 | 84 | 85 |
86 | 87 |
days of data.
88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | '); 102 | echo(''); 103 | echo(''); 104 | echo(''); 116 | echo(''); 117 | } 118 | ?> 119 | 120 |
DateDNSStatus
'.date("Y-m-d h:i a",strtotime($row['monitorTime'])).''.$row['rDNS'].''); 105 | if($row['isBlocked']==1){ 106 | $s = unserialize($row['status']); 107 | foreach($s as $r){ 108 | if(isset($r[0])) echo htmlentities($r[0]); 109 | if(isset($r[1])) echo ' - '. htmlentities($r[1]); 110 | echo "
"; 111 | } 112 | }else{ 113 | echo('OK'); 114 | } 115 | echo('
121 |
122 | 123 | 124 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | '', 17 | 'result'=>array(), 18 | ); 19 | 20 | $id = Utilities::validateLogin($username, $passwd, true, $apiKey); 21 | if($id == 0) { 22 | $result['status'] = 'invalid login'; 23 | output(); 24 | } 25 | 26 | switch($type){ 27 | case 'updateDomains': 28 | if($groupName=='') { 29 | $result['status'] = 'groupName is required'; 30 | break; 31 | } 32 | $id = Utilities::ensureGroupExists($groupName); 33 | Utilities::updateDomains($data, $id); 34 | $result['status'] = 'success'; 35 | break; 36 | 37 | case 'updateIPs': 38 | if($groupName=='') { 39 | $result['status'] = 'groupName is required'; 40 | break; 41 | } 42 | $id = Utilities::ensureGroupExists($groupName); 43 | Utilities::updateIPs($data, $id); 44 | $result['status'] = 'success'; 45 | break; 46 | 47 | case 'checkHostStatus': 48 | $result['status'] = 'success'; 49 | Utilities::setBlockLists(); 50 | $result['result'] = Utilities::checkBlacklists($data); 51 | break; 52 | 53 | case 'clearAllHostAndGroupData': 54 | $mysql = new _MySQL(); 55 | $mysql->connect(Setup::$connectionArray); 56 | $mysql->runQuery("truncate table monitors"); 57 | $mysql->runQuery("truncate table monitorGroup"); 58 | $result['status'] = 'success'; 59 | break; 60 | 61 | case 'blacklistStatus': 62 | $localCache = new _FileCache('blacklistmonitor-api', 90); 63 | $cacheKey = md5("$username|$passwd|$apiKey|$type|$data"); 64 | $cacheData = $localCache->get($cacheKey); 65 | if ($cacheData !== false) { 66 | output($cacheData); 67 | } 68 | $mysql = new _MySQL(); 69 | $mysql->connect(Setup::$connectionArray); 70 | $searchSQL = ''; 71 | switch($data){ 72 | case 'changed': 73 | $searchSQL .= " and lastStatusChanged = 1 "; 74 | break; 75 | case 'blocked': 76 | $searchSQL .= " and isBlocked = 1 "; 77 | break; 78 | case 'clean': $searchSQL .= " and isBlocked = 0 "; 79 | break; 80 | case 'all': 81 | default: 82 | } 83 | 84 | $rs = $mysql->runQuery(" 85 | select ipDomain,isBlocked,rDNS,status,lastStatusChangeTime,lastUpdate 86 | from monitors 87 | where 1=1 $searchSQL"); 88 | $result['status'] = 'success'; 89 | $result['result'] = array(); 90 | while($row = mysqli_fetch_array($rs)){ 91 | $result['result'][] = array( 92 | 'host'=>$row['ipDomain'], 93 | 'isBlocked'=>$row['isBlocked'], 94 | 'dns'=>$row['rDNS'], 95 | 'status'=>unserialize($row['status']), 96 | 'lastChanged'=>$row['lastStatusChangeTime'], 97 | 'lastChecked'=>$row['lastUpdate'], 98 | ); 99 | } 100 | $mysql->close(); 101 | $localCache->set($cacheKey, $result); 102 | break; 103 | 104 | default: 105 | $result['status'] = 'no such method'; 106 | } 107 | 108 | output(); 109 | 110 | function output($data = false){ 111 | global $result; 112 | if($data!==false){ 113 | echo(json_encode($data)); 114 | }else{ 115 | echo(json_encode($result)); 116 | } 117 | exit(); 118 | } 119 | -------------------------------------------------------------------------------- /service/blacklistmonitor.php: -------------------------------------------------------------------------------- 1 | #!/bin/php 2 | connect(Setup::$connectionArray); 48 | if(!Utilities::is_process_running($userProcessId)){ 49 | $userCheck = $mysql->runQueryReturnVar("select username from users where beenChecked = 0"); 50 | if($userCheck!==false){ 51 | $cmd = 'php '.dirname(__FILE__).'/userJob.php -i '.$parentProcessId; 52 | $userProcessId = Utilities::run_in_background($cmd); 53 | } 54 | } 55 | } catch (Exception $e) { 56 | _Logging::appLog($e->getMessage()); 57 | } 58 | if(!Utilities::is_process_running($monitorProcessesId)){ 59 | $cmd = 'php '.dirname(__FILE__).'/blacklistmonitor.php -p monitorProcessWatch -i '.$parentProcessId; 60 | $monitorProcessesId = Utilities::run_in_background($cmd); 61 | } 62 | sleep(15);//15 seconds 63 | } 64 | 65 | } 66 | 67 | function monitorProcessWatch($parentProcessId){ 68 | 69 | $m = new _MeasurePerformance(); 70 | $mysql = new _MySQL(); 71 | $mysql->connect(Setup::$connectionArray); 72 | 73 | $parallelProcessesMonitors = Setup::$settings['max_monitor_processes']; 74 | 75 | $monitorProcesses = array(); 76 | $processCountMonitors = 0; 77 | 78 | $ipDomain = false; 79 | 80 | while (true) { 81 | // are we still running? 82 | if(!Utilities::is_process_running($parentProcessId)){ 83 | _Logging::appLog("Parent Stopped - monitorStartWatch exited"); 84 | exit(); 85 | } 86 | 87 | $processCountMonitors = count($monitorProcesses); 88 | 89 | if ($processCountMonitors < $parallelProcessesMonitors){ 90 | $ipDomain = Utilities::getNextMonitor($mysql); 91 | if ($ipDomain!==false) { 92 | // start it 93 | $cmd = 'php '.dirname(__FILE__).'/monitorJob.php -h '.escapeshellarg($ipDomain); 94 | $pid = Utilities::run_in_background($cmd); 95 | $m->work(1); 96 | $monitorProcesses[] = $pid; 97 | } 98 | } 99 | 100 | // was there any work? 101 | if($ipDomain===false){ 102 | sleep(10);//10 seconds 103 | }else{ 104 | usleep(10000);//ideal time 10ms 105 | } 106 | 107 | // delete finished processes 108 | for ($x = 0; $x < $processCountMonitors; $x++) { 109 | if(isset($monitorProcesses[$x])){ 110 | if(!Utilities::is_process_running($monitorProcesses[$x])){ 111 | unset($monitorProcesses[$x]); 112 | } 113 | } 114 | } 115 | 116 | // fix array index 117 | $monitorProcesses = array_values($monitorProcesses); 118 | 119 | $processCountMonitors = count($monitorProcesses); 120 | 121 | //randomly reset counter every now and then 122 | if(mt_rand(1,2000)==1){ 123 | $m->endWork(); 124 | _Logging::appLog("App Avg Hosts/sec: {$m->avgPerformance}\tMonitor Threads: $processCountMonitors/$parallelProcessesMonitors"); 125 | $m = new _MeasurePerformance(); 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlacklistMonitor 2 | ``` 3 | Copyright (c) by respective owners. All rights reserved. Released under license as described in the file LICENSE.txt 4 | ``` 5 | Application for monitoring Domains and IPs on RBLs. With blacklistmonitor you can monitor and document IP ranges and domain names for showing up on RBL servers. It is intended for ISPs, Web Hosting, anyone who provides IP space to monitor and protect their networks. 6 | 7 | [![Build Status](https://scrutinizer-ci.com/g/mikelynn2/blacklistmonitor/badges/build.png?b=master)](https://scrutinizer-ci.com/g/mikerlynn/blacklistmonitor/build-status/master) 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mikelynn2/blacklistmonitor/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mikelynn2/blacklistmonitor/?branch=master) 9 | 10 | ## Docker Version 11 | - This is a useful unoffical docker container from a 3rd party. [Docker Download](https://hub.docker.com/r/grinay/blacklistmonitor/) 12 | 13 | ## Features 14 | - Supports All Major Blacklists. You can customize this list 15 | - Monitor blocks of IPs in CIDR Format and your domains 16 | - Web based reporting 17 | - API for integration or access the mysql database directly 18 | - Email, SMS, and Twitter Alerts 19 | 20 | ## Prerequisite software 21 | - [MySQL](http://www.MySQL.org) or [MariaDB](https://mariadb.org/) are needed for the database. 22 | - Most likely you'll need your own DNS server as well. You can use [Bind](https://www.isc.org/downloads/bind/) or even [unbound](https://www.unbound.net/). Bind is easier, unbound may be faster. You can attempt to use your ISPs name servers (see your /etc/resolve.conf). Some large ISPs name servers won't work and you'll need to run your own. Blacklistmonitor will not by default use your OS name servers. 23 | - Apache or Nginx 24 | - SMTP Mail server like postfix 25 | 26 | ## Installation Ubuntu Server 14.04 LTS 27 | ``` 28 | #install 29 | apt-get -y install apache2 30 | apt-get -y install mariadb-server mariadb-client mariadb-common 31 | apt-get -y install php5 php5-mysqlnd php5-cli php5-curl 32 | apt-get -y install bind9 dnsutils 33 | 34 | #set to start on boot 35 | update-rc.d bind9 defaults 36 | update-rc.d apache2 defaults 37 | update-rc.d mysql defaults 38 | ``` 39 | 40 | Go into the directory you want to install BlacklistMonitor into and clone the git repo. Usually this would be a web server directory like /var/www/html/. The rest of the commands below assume you're using this dir and the default config files do as well. 41 | 42 | ``` 43 | cd /var/www/html/ 44 | git clone git://github.com/mikelynn2/blacklistmonitor.git 45 | ``` 46 | 47 | ## Initialize Data 48 | ``` 49 | mysql -p < /var/www/html/blacklistmonitor/setup/blacklistmonitor.sql 50 | ``` 51 | 52 | ## Setup Apache 53 | ``` 54 | cp /var/www/html/blacklistmonitor/setup/blacklistmonitor-apache.conf /etc/apache2/sites-enabled/ 55 | ``` 56 | 57 | ## Copy Default Config 58 | ``` 59 | cp /var/www/html/blacklistmonitor/setup/blacklistmonitor.cfg /etc/ 60 | ``` 61 | 62 | After you've copied the config file you need to edit it to customize it for your setup here: /etc/blacklistmonitor.cfg 63 | 64 | Don't even try to use google or opendns public DNS servers. Many RBLs block these from queries. 65 | 66 | 67 | ## Schedule Cron 68 | Add the contents of this file into cron 69 | ``` 70 | cat /var/www/html/blacklistmonitor/setup/blacklistmonitor.cron 71 | ``` 72 | edit crontab 73 | ``` 74 | crontab -e 75 | ``` 76 | 77 | ## Service 78 | ``` 79 | cp /var/www/html/blacklistmonitor/setup/blacklistmonitor-ubuntu-upstart.conf /etc/init/blacklistmonitor.conf 80 | ``` 81 | 82 | ### start/stop/restart 83 | ``` 84 | start blacklistmonitor 85 | stop blacklistmonitor 86 | restart blacklistmonitor 87 | ``` 88 | 89 | ### Website 90 | The default username and password to the portal is admin/pa55w0rd 91 | It's recommended to change both. Especially if you're installing this on a public network. 92 | 93 | ### Timezone Setup 94 | ``` 95 | dpkg-reconfigure tzdata 96 | ``` 97 | Then edit your the value for date.timezone in /etc/php5/apache2/php.ini 98 | 99 | 100 | Watch your log for issues/performance 101 | ``` 102 | tail -f /var/log/blacklistmonitor.log 103 | ``` 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /classes/_FileCache.class.php: -------------------------------------------------------------------------------- 1 | path = '/tmp/_FileCache_/'.(string)$path; 26 | 27 | if ($expirationTimeInSeconds < 1) $expirationTimeInSeconds = 1; 28 | $this->expirationTimeInSeconds = (int)$expirationTimeInSeconds; 29 | 30 | if ($cleanInterval < 1) $cleanInterval = 1; 31 | $this->cleanInterval = (int)$cleanInterval; 32 | 33 | if ($directoryDepth<1) $directoryDepth=1; else if ($directoryDepth>10) $directoryDepth=10; 34 | $this->directoryDepth = (int)$directoryDepth; 35 | } 36 | 37 | // Get an object from the cache. 38 | // Parameters: 39 | // $key: The cache key. 40 | // Returns: 41 | // The object which was previously stored, or false if a cache miss occurred. 42 | public function get($key) { 43 | //return false; 44 | if (($this->cleanInterval == 1) || 45 | (rand(1, $this->cleanInterval) == $this->cleanInterval)) { 46 | $this->clean(); 47 | } 48 | $val = false; 49 | $fn = $this->getCacheFilename($key); 50 | $exptime = time()-$this->expirationTimeInSeconds; 51 | $fileData = @file_get_contents($fn); 52 | if ($fileData!==false && @file_exists($fn) && (@filemtime($fn) > $exptime)) { 53 | $val = unserialize($fileData); 54 | } 55 | return $val; 56 | } 57 | 58 | // Store an object into the cache. 59 | // Parameters: 60 | // $key: The cache key. 61 | // $value: The object to store. 62 | public function set($key, $value) { 63 | if (($this->cleanInterval == 1) || 64 | (rand(1, $this->cleanInterval) == $this->cleanInterval)) { 65 | $this->clean(); 66 | } 67 | $fn = $this->getCacheFilename($key, true); 68 | $fileData = file_put_contents($fn,serialize($value)); 69 | } 70 | 71 | // Delete an object from the cache. 72 | // Parameters: 73 | // $key: The cache key. 74 | public function delete($key) { 75 | $fn = $this->getCacheFilename($key); 76 | 77 | // Delete the cache file. 78 | if (@unlink($fn)) { 79 | // Delete empty subdirectories, all the way up to but excluding the top-level cache dir. 80 | $refPath = rtrim($this->path, "/\\"); 81 | $dir = rtrim(dirname($fn), "/\\"); 82 | for ($i = 0; $i < $this->directoryDepth; $i++) { 83 | if (!@rmdir($dir)) break; 84 | $dir = rtrim(dirname($dir), "/\\"); 85 | } 86 | } 87 | } 88 | 89 | // Clean expired entries. 90 | public function clean() { 91 | $this->cleanPath($this->path); 92 | } 93 | 94 | // Clean expired entries from a directory. 95 | public function cleanPath($path) { 96 | $exptime = time()-$this->expirationTimeInSeconds; 97 | foreach (@glob($path.'/*', GLOB_NOSORT) as $fn) { 98 | if (@is_dir($fn)) { 99 | $this->cleanPath($fn); 100 | } else if (@filemtime($fn) <= $exptime) { 101 | @unlink($fn); 102 | } 103 | } 104 | if ($path != $this->path) { 105 | @rmdir($path); 106 | } 107 | } 108 | 109 | private function getCacheFilename($key, $autoCreateDirectory = false) { 110 | $hash = sha1($key); 111 | $path = $this->path; 112 | for ($i = 0, $idx = 0; $i < $this->directoryDepth; $i++, $idx += 2) { 113 | $path .= '/'.substr($hash, $idx, 2); 114 | } 115 | if ($autoCreateDirectory) @mkdir($path, 0777, true); 116 | $path .= '/'.$hash; 117 | return $path; 118 | } 119 | 120 | public function fixPermissions($user) { 121 | $cmd = escapeshellcmd('chown -R '.$user.':'.$user. ' ' . $this->path); 122 | exec($cmd); 123 | } 124 | 125 | 126 | } 127 | -------------------------------------------------------------------------------- /blockLists.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 18 | if($host != ''){ 19 | if($toggle==0){ 20 | $mysql->runQuery(" 21 | update blockLists 22 | set isActive = '0' 23 | where md5(host) = '".$mysql->escape($host)."'"); 24 | }else{ 25 | $mysql->runQuery(" 26 | update blockLists 27 | set isActive = '1' 28 | where md5(host) = '".$mysql->escape($host)."'"); 29 | } 30 | exit(); 31 | } 32 | 33 | $sql = " 34 | select * 35 | from blockLists 36 | order by isActive desc, blocksToday desc 37 | "; 38 | $rs = $mysql->runQuery($sql); 39 | 40 | include('header.inc.php'); 41 | include('accountSubnav.inc.php'); 42 | ?> 43 | 44 | 45 | 46 | 76 | 77 |
78 |
79 | - Enabled
80 | - Disabled
81 |
82 |
83 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | '); 103 | echo(''); 106 | }else{ 107 | echo(''); 108 | } 109 | echo(''); 110 | echo(''); 111 | echo(''); 112 | echo(''); 119 | echo(''); 120 | echo(''); 121 | echo(''); 122 | echo(''); 123 | echo(''); 124 | } 125 | $mysql->close(); 126 | ?> 127 | 128 |
StatusBlacklistTypeDescriptionImportanceBlocks TodayClean TodayBlocks YestClean Yest
'); 104 | if($row['isActive']==0){ 105 | echo(''.$row['host'].''.($row['monitorType']=='ip' ? 'IP' : 'Domain').''.$row['description'].''); 113 | switch($row['importance']){ 114 | case 3: echo('High'); break; 115 | case 2: echo('Medium'); break; 116 | case 1: echo('Low'); break; 117 | } 118 | echo(''.number_format($row['blocksToday'],0).''.number_format($row['cleanToday'],0).''.number_format($row['blocksYesterday'],0).''.number_format($row['cleanYesterday'],0).'
129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /editHostGroup.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 25 | 26 | 27 | if($deleteGroup!=''){ 28 | $mysql->runQuery("delete from monitorGroup where id = $id"); 29 | $mysql->runQuery("delete from monitors where monitorGroupId = $id"); 30 | echo(""); 31 | exit(); 32 | } 33 | 34 | if (isset($_POST["submit"])) { 35 | 36 | //TODO: make sure blacklists are domains with an ip address on them 37 | if($id !== 0){ 38 | //update 39 | $mysql->runQuery(" 40 | update monitorGroup set groupName = '".$mysql->escape($groupName)."', 41 | ips = '".$mysql->escape($ips)."', 42 | domains = '".$mysql->escape($domains)."' 43 | where id = $id 44 | "); 45 | }else{ 46 | $mysql->runQuery(" 47 | insert into monitorGroup set groupName = '".$mysql->escape($groupName)."', 48 | ips = '".$mysql->escape($ips)."', 49 | domains = '".$mysql->escape($domains)."' 50 | "); 51 | $id = $mysql->identity; 52 | } 53 | Utilities::updateDomains($domains, $id); 54 | Utilities::updateIPs($ips, $id); 55 | echo(""); 56 | exit(); 57 | } 58 | 59 | $group = array( 60 | 'groupName'=>'', 61 | 'ips'=>'', 62 | 'domains'=>'', 63 | ); 64 | $rs = $mysql->runQuery("select * from monitorGroup where id = $id"); 65 | while($row = mysqli_fetch_array($rs)){ 66 | $group = $row; 67 | } 68 | 69 | ?> 70 | 71 | 72 | 73 | 74 | 84 | 85 |
86 |
87 | 88 |
89 | 90 |
91 |
92 |
93 | 94 |
95 | 96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 |
104 |
105 |
106 | 107 | 0) { ?> 108 |
109 |
110 | 111 |
112 | 113 |



114 | 115 | 116 | -------------------------------------------------------------------------------- /classes/_IpAddresses.class.php: -------------------------------------------------------------------------------- 1 | 32) ) { 95 | // Invalid bit count. 96 | return false; 97 | } else if ($bitCount == 0) { 98 | // << 32 in PHP doesn't seem to do anything; so we work around it here. 99 | $mask = 0; 100 | } else if ($bitCount == 32) { 101 | // Prevent << 0, which effects no change. 102 | $mask = 0xffffffff; 103 | } else { 104 | $mask = (0xffffffff << (32 - $bitCount)) & 0xffffffff; 105 | } 106 | } else { 107 | // Invalid dot count in mask. 108 | return false; 109 | } 110 | // In order for the IP address to be in the network, the biwise-and of the IP address 111 | // with the netmask, must equal the bitwise-and of the network address with the netmask. 112 | return ($ipLong & $mask) == ($networkLong & $mask); 113 | } 114 | 115 | public static function getRemoteIP() { 116 | return (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''); 117 | } 118 | 119 | public static function getAllIPsFromString($str, $dedup = true) { 120 | // Find ips in a string and return array of them 121 | $r = "/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/"; 122 | $matches = array(); 123 | preg_match_all($r, $str, $matches); 124 | $matches = (isset($matches[0]) ? $matches[0] : array()); 125 | return ($dedup ? array_unique($matches) : $matches); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /apiDocumentation.php: -------------------------------------------------------------------------------- 1 | 17 |

API Documentation

18 |

19 | The API is a JSON HTTP POST based API. 20 | All responses from the API are JSON. 21 |

22 | 23 |

POST URL: /api.php

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Post Variable:
apiKeyYour api key:
type 36 | updateDomains - pass data var of whitespace delimited domains. Whitespace can be any tabs, spaces, new lines.

37 | updateIPs - pass data var of whitespace delimited ip addresses/ranges. Whitespace can be any tabs, spaces, new lines.

38 | checkHostStatus - pass data var of a single ip (not a range) or domain name for current black list status.

39 | blacklistStatus - data var - all (default) | blocked | changed | clean. Returns blacklist status of all current ips and domains

40 | clearAllHostAndGroupData - Clears out all hosts and host groups. Doesn't delete host history though. 41 |
dataWhen calling functions that need data passed use the var data.
groupNameWhen calling updateDomains or updateIPs, also pass the groupName var they should belong to. If the group doesn't exist it will be auto created.
52 | 53 |
54 |

Response

55 |

56 | Below is an example response from the API in JSON. It will always include a status. Either success or failure and if a result is required for the call it will be included as well as with the 57 | blacklistStatus call. 58 |

59 | 60 |
 61 | {"status":"success","result":""}
 62 | 
63 | 64 |

PHP Example - Pulling all hosts current status

65 |
66 |
 67 | <?php
 68 | $apiKey = '';
 69 | 
 70 | $requestBody =
 71 | 	"apiKey=".urlencode($apiKey).
 72 | 	"&type=blacklistStatus".
 73 | 	"&data=all";
 74 | 
 75 | $return = httpPost('/api.php', $requestBody);
 76 | $return = json_decode($return, true);
 77 | $results = $return['result'];
 78 | 
 79 | $body = "";
 80 | $body .= "<table border='1'>";
 81 | $body .= "<tr>";
 82 | $body .= "<th>host</th>";
 83 | $body .= "<th>status</th>";
 84 | $body .= "</tr>";
 85 | 
 86 | foreach($results as $result){
 87 | 	$body .= "<tr>";
 88 | 	$body .= "<td>".htmlentities($result['host'])."</td>";
 89 | 	$body .= "<td nowrap>";
 90 | 	if($result['isBlocked']==0){
 91 | 		$body .= "OK";
 92 | 	}else{
 93 | 		foreach($result['status'] as $r){
 94 | 			if($r[1] == false || $r[1] == ''){
 95 | 
 96 | 			}else{
 97 | 				$body .= htmlentities($r[0]) . " - " . htmlentities($r[1])."<br>";
 98 | 			}
 99 | 		}
100 | 	}
101 | 	$body .= "</td>";
102 | 	$body .= "</tr>";
103 | }
104 | 
105 | $body .= "</table>";
106 | 
107 | echo $body;
108 | 
109 | function httpPost($url, $vars){
110 | 	//open connection
111 | 	$ch = curl_init();
112 | 
113 | 	//set the url, number of POST vars, POST data
114 | 	curl_setopt($ch,CURLOPT_URL,$url);
115 | 	curl_setopt($ch,CURLOPT_POST,true);
116 | 	curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
117 | 	curl_setopt($ch,CURLOPT_FAILONERROR,true);
118 | 	curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
119 | 	curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
120 | 	//execute post
121 | 	return 	curl_exec($ch);
122 | }
123 | 
124 | 125 |


126 | 127 | 128 | 129 | 130 |

Callback API

131 | 132 |

133 | A JSON array will be posted for each host upon a status change with that host. Each host will be called back in seperate requests. You set the call back URL on your profile page. 134 |

135 | 136 |

Example Posted JSON

137 |
138 | {
139 | 	"host":"samplehosttest.com",
140 | 	"isBlocked":true,
141 | 	"rDNS":"reverse-dns-sample.samplehosttest.com",
142 | 	"blocks":[
143 | 		["l2.apews.org","Listed at APEWS-L2 - visit http:\/\/www.apews.org\/?page=test&C=131&E=1402188&ip=127.0.0.1"],
144 | 		["b.barracudacentral.org","127.0.0.2"]
145 | 	]
146 | }
147 | 
148 | 149 |


150 |


151 | 152 | 155 | -------------------------------------------------------------------------------- /service/userJob.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | connect(Setup::$connectionArray); 31 | 32 | // get the user data 33 | $user = Utilities::getAccount(); 34 | 35 | _Logging::appLog("user job started"); 36 | 37 | // get the accounts blacklists 38 | Utilities::setBlockLists(); 39 | 40 | if( (empty(Utilities::$domainBlacklists)===true) && (empty(Utilities::$ipBlacklists)===true) ){ 41 | _Logging::appLog("no blacklists configured"); 42 | // mark this one as ran 43 | $mysql->runQuery("update users set beenChecked = 1, lastChecked = '".date('Y-m-d H:i:s')."'"); 44 | exit(); 45 | } 46 | 47 | //anything to monitor? 48 | $monitorCount = Utilities::getHostCount($mysql); 49 | if($monitorCount==0){ 50 | _Logging::appLog("nothing to monitor"); 51 | exit(); 52 | } 53 | 54 | // reset checks 55 | $mysql->runQuery("update monitors set beenChecked = 0"); 56 | 57 | // wait for results 58 | while(true){ 59 | if(!Utilities::is_process_running($parentProcessId)){ 60 | _Logging::appLog("parent died - userJob exited"); 61 | exit(); 62 | } 63 | $rs = $mysql->runQuery("select ipDomain from monitors where beenChecked = 0 limit 1;"); 64 | if($row = mysqli_fetch_array($rs)){ 65 | sleep(4);//wait 4 seconds for them to finish 66 | }else{ 67 | break; 68 | } 69 | } 70 | 71 | $m->endWork(); 72 | 73 | $lastRunTime = (int)$m->runTime; 74 | 75 | // mark this one as ran 76 | $mysql->runQuery("update users set beenChecked = 1, lastChecked = '".date('Y-m-d H:i:s')."', lastRunTime = $lastRunTime"); 77 | 78 | // basic stats 79 | $hostsChanged = Utilities::getHostChangeCount($mysql); 80 | $errorHosts = Utilities::getHostErrorCount($mysql); 81 | $newErrorHosts = Utilities::getHostErrorCount($mysql, 0, true); 82 | $newCleanHosts = Utilities::getHostCleanCount($mysql, 0, true); 83 | 84 | 85 | if($hostsChanged > 0 && $user['disableEmailNotices']==0){ 86 | $table = ""; 87 | $summary = ""; 88 | $summaryText = ""; 89 | $noticeMessage = ""; 90 | $url = Setup::$settings['base_url']; 91 | 92 | $summary .= "
"; 93 | $summary .= "Total: ".number_format($monitorCount)."
"; 94 | $summary .= "Clean: ".number_format(($monitorCount-$errorHosts))."
"; 95 | $summary .= "Blocked: ".number_format($errorHosts)."
"; 96 | $summary .= "New Blocked: ".number_format($newErrorHosts)."
"; 97 | $summary .= "New Clean: ".number_format($newCleanHosts)."
"; 98 | $summary .= ''; 99 | $summary .= "
"; 100 | 101 | $summary .= "
Monitor Groups

"; 102 | 103 | if( (isset(Setup::$settings['email_report_detailed_host_changes'])) 104 | && (Setup::$settings['email_report_detailed_host_changes']==true) ){ 105 | $table .= '
'; 106 | $table .= 'New Blocks
'; 107 | $rs = $mysql->runQuery("select m.ipDomain, mg.groupName FROM monitors m inner join monitorGroup mg on mg.id = m.monitorGroupId where m.isBlocked = 1 and m.lastStatusChanged = 1 order by m.isDomain desc, m.ipDomain"); 108 | $table .= ''; 109 | while($row = mysqli_fetch_array($rs)){ 110 | $table .= ''; 111 | $table .= ''; 112 | $table .= ''; 113 | } 114 | $table .= '
'.$row['ipDomain'].''.$row['groupName'].'
'; 115 | $table .= '

'; 116 | $table .= '
'; 117 | $table .= 'New Clean
'; 118 | $rs = $mysql->runQuery("select m.ipDomain, mg.groupName FROM monitors m inner join monitorGroup mg on mg.id = m.monitorGroupId where m.isBlocked = 0 and m.lastStatusChanged = 1 order by m.isDomain desc, m.ipDomain"); 119 | $table .= ''; 120 | while($row = mysqli_fetch_array($rs)){ 121 | $table .= ''; 122 | $table .= ''; 123 | $table .= ''; 124 | } 125 | $table .= '
'.$row['ipDomain'].''.$row['groupName'].'
'; 126 | } 127 | 128 | $footer = "
Manage your account
"; 129 | 130 | $summaryText .= "Total: ".number_format($monitorCount)."\n"; 131 | $summaryText .= "Clean: ".number_format(($monitorCount-$errorHosts))."\n"; 132 | $summaryText .= "Blocked: ".number_format($errorHosts)."\n"; 133 | $summaryText .= "New Blocked: ".number_format($newErrorHosts)."\n"; 134 | $summaryText .= "New Clean: ".number_format($newCleanHosts)."\n"; 135 | 136 | $e = explode("\n",$user['noticeEmailAddresses']); 137 | if( (count($e) > 0) && (Setup::$settings['smtp_server']!='') ){ 138 | // regular email 139 | $mail = new PHPMailer(); 140 | $mail->IsSMTP(); 141 | $mail->Host = Setup::$settings['smtp_server']; 142 | $mail->From = Setup::$settings['from_email']; 143 | $mail->FromName = Setup::$settings['from_name']; 144 | foreach($e as $a){ 145 | if(trim($a)!=''){ 146 | $mail->AddAddress($a); 147 | } 148 | } 149 | $mail->Subject = Setup::$settings['alert_subject']; 150 | $mail->isHtml(true); 151 | $mail->Body = "$noticeMessage $summary $table $footer"; 152 | $mail->Send(); 153 | } 154 | 155 | // text message 156 | $e = explode("\n",$user['textMessageEmails']); 157 | if( (count($e) > 0) && (Setup::$settings['smtp_server']!='') ){ 158 | $mail = new PHPMailer(); 159 | $mail->IsSMTP(); 160 | $mail->Host = Setup::$settings['smtp_server']; 161 | $mail->From = Setup::$settings['from_email']; 162 | $mail->FromName = Setup::$settings['from_name']; 163 | foreach($e as $a){ 164 | if(trim($a)!=''){ 165 | $mail->AddAddress($a); 166 | } 167 | } 168 | $mail->Subject = Setup::$settings['alert_subject_sms']; 169 | $mail->isHtml(false); 170 | $mail->Body = "$url/m.php $summaryText"; 171 | $mail->Send(); 172 | } 173 | 174 | if($user['twitterHandle']!=''){ 175 | $t = new Twitter(); 176 | $t->message($user['twitterHandle'], $summaryText); 177 | } 178 | 179 | _Logging::appLog("user alert sent"); 180 | 181 | } 182 | 183 | _Logging::appLog("user job ended"); 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /hosts.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 22 | $searchSQL = ""; 23 | $hostTypeSQL = ""; 24 | $orderSQL = " order by "; 25 | $limitSQL = ($limit > 0) ? " limit $limit " : ''; 26 | switch($oc){ 27 | case 1: 28 | $searchSQL .= " and lastStatusChanged = 1 "; 29 | $orderSQL .= " lastStatusChangeTime desc "; 30 | break; 31 | case 2: 32 | $searchSQL .= " and isBlocked = 1 "; 33 | $orderSQL .= " lastStatusChangeTime desc "; 34 | break; 35 | case 3: 36 | $searchSQL .= " and isBlocked = 0 "; 37 | $orderSQL .= " lastStatusChangeTime desc "; 38 | break; 39 | default: 40 | $searchSQL .= " "; 41 | $orderSQL .= " lastStatusChangeTime desc "; 42 | break; 43 | } 44 | 45 | if($monitorGroupId != 0) $searchSQL .= " and monitorGroupId = $monitorGroupId "; 46 | 47 | switch($hostType){ 48 | case 'domains': 49 | $hostTypeSQL .= " and isDomain = 1 "; 50 | break; 51 | case 'ips': 52 | $hostTypeSQL .= " and isDomain = 0 "; 53 | break; 54 | } 55 | 56 | if($searchS != ''){ 57 | $searchSQL .= " and ( 58 | ipDomain like '%".$mysql->escape($searchS)."%' 59 | or rDNS like '%".$mysql->escape($searchS)."%' 60 | or status like '%".$mysql->escape($searchS)."%' ) "; 61 | } 62 | $sql = " 63 | select m.isBlocked, m.lastUpdate, m.ipDomain, m.lastStatusChangeTime, m.rDNS, m.status, g.groupName, g.id 64 | from monitors m 65 | inner join monitorGroup g on g.id = m.monitorGroupId 66 | where 1=1 $hostTypeSQL $searchSQL 67 | $orderSQL 68 | $limitSQL 69 | "; 70 | 71 | $rs = $mysql->runQuery($sql); 72 | 73 | include('header.inc.php'); 74 | include('accountSubnav.inc.php'); 75 | 76 | 77 | $hostsCount = Utilities::getHostCount($mysql, $monitorGroupId); 78 | $hostsCountError = Utilities::getHostErrorCount($mysql, $monitorGroupId); 79 | ?> 80 | 81 | 82 | 83 | 97 | 98 | 122 | 123 |
124 | 125 |
126 |
127 | 130 | 133 | 136 | 139 |
140 |
141 | 147 |
148 |
149 |
150 |
151 | 156 |
157 |
158 |
159 | 160 | 161 |
162 | 163 | 164 |
165 |
166 | 167 |
168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | '); 183 | echo(''); 184 | echo(''); 185 | if('0000-00-00 00:00:00'==$row['lastUpdate']){ 186 | echo(''); 187 | echo(''); 188 | echo(''); 189 | echo(''); 190 | }else{ 191 | echo(''); 194 | if('0000-00-00 00:00:00'==$row['lastStatusChangeTime']){ 195 | echo(''); 196 | }else{ 197 | echo(''); 200 | } 201 | echo(''); 202 | echo(''); 215 | } 216 | echo(''); 217 | } 218 | $mysql->close(); 219 | ?> 220 | 221 |
HostGroupLast CheckedLast ChangeDNSCurrent Status
'.$row['ipDomain'].''.$row['groupName'].''.Utilities::$hostNotCheckedMessage.''.Utilities::$hostNotCheckedMessage.''.Utilities::$hostNotCheckedMessage.''.Utilities::$hostNotCheckedMessage.''); 192 | echo(date("Y-n-j g:i a",strtotime($row['lastUpdate']))); 193 | echo('n/a'); 198 | echo(date("Y-n-j g:i a",strtotime($row['lastStatusChangeTime']))); 199 | echo(''.$row['rDNS'].''); 203 | if($row['isBlocked']==1){ 204 | $s = unserialize($row['status']); 205 | foreach($s as $r){ 206 | if($r[1] == false || $r[1] == ''){ 207 | }else{ 208 | echo htmlentities($r[0]) . " - " . htmlentities($r[1])."
\n"; 209 | } 210 | } 211 | }else{ 212 | echo('OK'); 213 | } 214 | echo('
222 |
223 | 224 | 225 | -------------------------------------------------------------------------------- /classes/TwitterAPIExchange.php: -------------------------------------------------------------------------------- 1 | 11 | * @license MIT License 12 | * @link http://github.com/j7mbo/twitter-api-php 13 | */ 14 | class TwitterAPIExchange 15 | { 16 | private $oauth_access_token; 17 | private $oauth_access_token_secret; 18 | private $consumer_key; 19 | private $consumer_secret; 20 | private $postfields; 21 | private $getfield; 22 | protected $oauth; 23 | public $url; 24 | 25 | /** 26 | * Create the API access object. Requires an array of settings:: 27 | * oauth access token, oauth access token secret, consumer key, consumer secret 28 | * These are all available by creating your own application on dev.twitter.com 29 | * Requires the cURL library 30 | * 31 | * @param array $settings 32 | */ 33 | public function __construct(array $settings) 34 | { 35 | if (!in_array('curl', get_loaded_extensions())) 36 | { 37 | throw new Exception('You need to install cURL, see: http://curl.haxx.se/docs/install.html'); 38 | } 39 | 40 | if (!isset($settings['oauth_access_token']) 41 | || !isset($settings['oauth_access_token_secret']) 42 | || !isset($settings['consumer_key']) 43 | || !isset($settings['consumer_secret'])) 44 | { 45 | throw new Exception('Make sure you are passing in the correct parameters'); 46 | } 47 | 48 | $this->oauth_access_token = $settings['oauth_access_token']; 49 | $this->oauth_access_token_secret = $settings['oauth_access_token_secret']; 50 | $this->consumer_key = $settings['consumer_key']; 51 | $this->consumer_secret = $settings['consumer_secret']; 52 | } 53 | 54 | /** 55 | * Set postfields array, example: array('screen_name' => 'J7mbo') 56 | * 57 | * @param array $array Array of parameters to send to API 58 | * 59 | * @return TwitterAPIExchange Instance of self for method chaining 60 | */ 61 | public function setPostfields(array $array) 62 | { 63 | if (!is_null($this->getGetfield())) 64 | { 65 | throw new Exception('You can only choose get OR post fields.'); 66 | } 67 | 68 | if (isset($array['status']) && substr($array['status'], 0, 1) === '@') 69 | { 70 | $array['status'] = sprintf("\0%s", $array['status']); 71 | } 72 | 73 | $this->postfields = $array; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Set getfield string, example: '?screen_name=J7mbo' 80 | * 81 | * @param string $string Get key and value pairs as string 82 | * 83 | * @return \TwitterAPIExchange Instance of self for method chaining 84 | */ 85 | public function setGetfield($string) 86 | { 87 | if (!is_null($this->getPostfields())) 88 | { 89 | throw new Exception('You can only choose get OR post fields.'); 90 | } 91 | 92 | $search = array('#', ',', '+', ':'); 93 | $replace = array('%23', '%2C', '%2B', '%3A'); 94 | $string = str_replace($search, $replace, $string); 95 | 96 | $this->getfield = $string; 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Get getfield string (simple getter) 103 | * 104 | * @return string $this->getfields 105 | */ 106 | public function getGetfield() 107 | { 108 | return $this->getfield; 109 | } 110 | 111 | /** 112 | * Get postfields array (simple getter) 113 | * 114 | * @return array $this->postfields 115 | */ 116 | public function getPostfields() 117 | { 118 | return $this->postfields; 119 | } 120 | 121 | /** 122 | * Build the Oauth object using params set in construct and additionals 123 | * passed to this method. For v1.1, see: https://dev.twitter.com/docs/api/1.1 124 | * 125 | * @param string $url The API url to use. Example: https://api.twitter.com/1.1/search/tweets.json 126 | * @param string $requestMethod Either POST or GET 127 | * @return \TwitterAPIExchange Instance of self for method chaining 128 | */ 129 | public function buildOauth($url, $requestMethod) 130 | { 131 | if (!in_array(strtolower($requestMethod), array('post', 'get'))) 132 | { 133 | throw new Exception('Request method must be either POST or GET'); 134 | } 135 | 136 | $consumer_key = $this->consumer_key; 137 | $consumer_secret = $this->consumer_secret; 138 | $oauth_access_token = $this->oauth_access_token; 139 | $oauth_access_token_secret = $this->oauth_access_token_secret; 140 | 141 | $oauth = array( 142 | 'oauth_consumer_key' => $consumer_key, 143 | 'oauth_nonce' => time(), 144 | 'oauth_signature_method' => 'HMAC-SHA1', 145 | 'oauth_token' => $oauth_access_token, 146 | 'oauth_timestamp' => time(), 147 | 'oauth_version' => '1.0' 148 | ); 149 | 150 | $getfield = $this->getGetfield(); 151 | 152 | if (!is_null($getfield)) 153 | { 154 | $getfields = str_replace('?', '', explode('&', $getfield)); 155 | foreach ($getfields as $g) 156 | { 157 | $split = explode('=', $g); 158 | $oauth[$split[0]] = $split[1]; 159 | } 160 | } 161 | 162 | $base_info = $this->buildBaseString($url, $requestMethod, $oauth); 163 | $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret); 164 | $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true)); 165 | $oauth['oauth_signature'] = $oauth_signature; 166 | 167 | $this->url = $url; 168 | $this->oauth = $oauth; 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * Perform the actual data retrieval from the API 175 | * 176 | * @param boolean $return If true, returns data. 177 | * 178 | * @return string json If $return param is true, returns json data. 179 | */ 180 | public function performRequest($return = true) 181 | { 182 | if (!is_bool($return)) 183 | { 184 | throw new Exception('performRequest parameter must be true or false'); 185 | } 186 | 187 | $header = array($this->buildAuthorizationHeader($this->oauth), 'Expect:'); 188 | 189 | $getfield = $this->getGetfield(); 190 | $postfields = $this->getPostfields(); 191 | 192 | $options = array( 193 | CURLOPT_HTTPHEADER => $header, 194 | CURLOPT_HEADER => false, 195 | CURLOPT_URL => $this->url, 196 | CURLOPT_RETURNTRANSFER => true, 197 | CURLOPT_TIMEOUT => 10, 198 | ); 199 | 200 | if (!is_null($postfields)) 201 | { 202 | $options[CURLOPT_POSTFIELDS] = $postfields; 203 | } 204 | else 205 | { 206 | if ($getfield !== '') 207 | { 208 | $options[CURLOPT_URL] .= $getfield; 209 | } 210 | } 211 | 212 | $feed = curl_init(); 213 | curl_setopt_array($feed, $options); 214 | $json = curl_exec($feed); 215 | curl_close($feed); 216 | 217 | if ($return) { return $json; } 218 | } 219 | 220 | /** 221 | * Private method to generate the base string used by cURL 222 | * 223 | * @param string $baseURI 224 | * @param string $method 225 | * @param array $params 226 | * 227 | * @return string Built base string 228 | */ 229 | private function buildBaseString($baseURI, $method, $params) 230 | { 231 | $return = array(); 232 | ksort($params); 233 | 234 | foreach($params as $key=>$value) 235 | { 236 | $return[] = "$key=" . $value; 237 | } 238 | 239 | return $method . "&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $return)); 240 | } 241 | 242 | /** 243 | * Private method to generate authorization header used by cURL 244 | * 245 | * @param array $oauth Array of oauth data generated by buildOauth() 246 | * 247 | * @return string $return Header used by cURL for request 248 | */ 249 | private function buildAuthorizationHeader($oauth) 250 | { 251 | $return = 'Authorization: OAuth '; 252 | $values = array(); 253 | 254 | foreach($oauth as $key => $value) 255 | { 256 | $values[] = "$key=\"" . rawurlencode($value) . "\""; 257 | } 258 | 259 | $return .= implode(', ', $values); 260 | return $return; 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /account.php: -------------------------------------------------------------------------------- 1 | connect(Setup::$connectionArray); 31 | 32 | // audit check frequency 33 | $checkFrequency = array_key_exists('checkFrequency', $_POST) ? $_POST['checkFrequency'] : ''; 34 | 35 | if($testUrl!=''){ 36 | if(Utilities::testAPICallback($testUrl)){ 37 | echo('true'); 38 | }else{ 39 | echo('false'); 40 | } 41 | exit(); 42 | } 43 | 44 | if (isset($_POST["submit"])) { 45 | if($passwd=='') $message[] = 'You must select a password.'; 46 | 47 | if($passwdOld != $passwd){ 48 | $passwdOld = md5($passwd); 49 | } 50 | 51 | $ta = explode("\n",$noticeEmailAddresses); 52 | $noticeEmailAddresses = ""; 53 | foreach($ta as $e){ 54 | $e = trim($e); 55 | if(Utilities::isValidEmail($e)){ 56 | $noticeEmailAddresses .= "$e\n"; 57 | } 58 | } 59 | $ta = explode("\n",$textMessageEmails); 60 | $textMessageEmails = ""; 61 | foreach($ta as $e){ 62 | $e = trim($e); 63 | if(Utilities::isValidEmail($e)){ 64 | $textMessageEmails .= "$e\n"; 65 | } 66 | } 67 | //TODO: make sure blacklists are domains with an ip address on them 68 | if(count($message) == 0){ 69 | //update 70 | $mysql->runQuery(" 71 | update users set username = '".$mysql->escape($username)."', 72 | passwd = '".$mysql->escape($passwdOld)."', 73 | apiKey = '".$mysql->escape($apiKey)."', 74 | twitterHandle = '".$mysql->escape($twitterHandle)."', 75 | twitterHandle = '".$mysql->escape($twitterHandle)."', 76 | lastUpdate = '".date('Y-m-d H:i:s')."', 77 | twitterHandle = '".$mysql->escape($twitterHandle)."', 78 | noticeEmailAddresses = '".$mysql->escape(trim($noticeEmailAddresses))."', 79 | textMessageEmails = '".$mysql->escape(trim($textMessageEmails))."', 80 | apiCallbackURL = '".$mysql->escape($apiCallbackURL)."', 81 | checkFrequency = '".$mysql->escape($checkFrequency)."', 82 | disableEmailNotices = $disableEmailNotices 83 | "); 84 | if($beenChecked==1){ 85 | $mysql->runQuery("update users set beenChecked = 0"); 86 | $message[] = "Check scheduled."; 87 | } 88 | if($twitterHandle!=''){ 89 | $t = new Twitter(); 90 | $t->follow($twitterHandle); 91 | } 92 | $message[] = "Account updated."; 93 | } 94 | } 95 | $user = Utilities::getAccount(); 96 | if(!$user){ 97 | //invalid account 98 | echo(""); 99 | exit(); 100 | } 101 | ?> 102 | 103 | 104 | 105 | 106 |
107 |
108 | 112 |
113 |
114 | 115 | 137 | 138 | $m
"); 141 | } 142 | ?> 143 | 144 |
145 |
146 | 147 |
148 | 149 |
150 |
151 |
152 | 153 |
154 | 155 |
156 |
157 |
158 | 159 |
160 | 161 |
162 |
163 |
164 | 165 |
166 | 173 |
174 |
175 |
176 | 178 |
179 | 180 |
181 |
182 |
183 | 184 |
185 | 186 |
187 |
188 |
189 | 190 |
191 |
192 | @ 193 | 194 |
195 |
196 |
197 |
198 | 201 |
202 |
203 | 204 | 205 | 206 | 207 |
208 |
error
209 |
210 |
211 |
212 | 213 |
214 | Disable Notices
You can pause receiving email/text alerts when your status changes. Useful if you have a frequently changing network. This does not pause api call backs. 215 |
216 |
217 |
218 | 219 |
220 | Request Immediate Check
Selecting this will request your hosts be checked as soon as possible. 221 |
222 |
223 |
224 |
225 | 226 |
227 |
228 | 229 |
230 | 231 |



232 | 233 | 234 | -------------------------------------------------------------------------------- /setup/blacklistmonitor.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `blacklistmonitor` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 2 | USE `blacklistmonitor`; 3 | 4 | 5 | DROP TABLE IF EXISTS `blockLists`; 6 | CREATE TABLE IF NOT EXISTS `blockLists` ( 7 | `host` varchar(100) NOT NULL, 8 | `monitorType` enum('ip','domain') NOT NULL, 9 | `functionCall` enum('rbl') NOT NULL DEFAULT 'rbl', 10 | `description` varchar(400) NOT NULL, 11 | `website` varchar(500) NOT NULL, 12 | `lastBlockReport` datetime NOT NULL, 13 | `importance` enum('1','2','3') NOT NULL DEFAULT '2', 14 | `isActive` enum('0','1') NOT NULL DEFAULT '1' COMMENT '0=inactive;1=active', 15 | `blocksToday` int(11) NOT NULL DEFAULT '0', 16 | `blocksYesterday` int(11) NOT NULL DEFAULT '0', 17 | `cleanToday` int(11) NOT NULL DEFAULT '0', 18 | `cleanYesterday` int(11) NOT NULL DEFAULT '0' 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 20 | 21 | -- 22 | -- Dumping data for table `blockLists` 23 | -- 24 | 25 | INSERT INTO `blockLists` (`host`, `monitorType`, `functionCall`, `description`, `website`, `lastBlockReport`, `importance`, `isActive`, `blocksToday`, `blocksYesterday`, `cleanToday`, `cleanYesterday`) VALUES 26 | ('0spam.fusionzero.com', 'ip', 'rbl', 'Hosts sending to spam traps', 'http://0spam.fusionzero.com', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 27 | ('0spamurl.fusionzero.com', 'ip', 'rbl', 'IPs found in links', 'http://0spam.fusionzero.com/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 28 | ('b.barracudacentral.org', 'ip', 'rbl', 'Barracuda spam filtering. One of the quicker reacting lists for spam and virus traffic coming from hosts.', 'http://www.barracudacentral.org', '0000-00-00 00:00:00', '3', '0', 0, 0, 0, 0), 29 | ('bl.blocklist.de', 'ip', 'rbl', 'Fail2ban reporting. SSH, mail, ftp, http, etc attacks.', 'http://www.blocklist.de/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 30 | ('bl.mailspike.net', 'ip', 'rbl', 'Uses seed system to collect stats about IPs.', 'http://www.mailspike.net/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 31 | ('bl.spamcannibal.org', 'ip', 'rbl', 'Seed system and complaint mails forwarded to them.', 'http://www.spamcannibal.org', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 32 | ('bl.spamcop.net', 'ip', 'rbl', 'Automated listings from user submitted spam reports.', 'http://www.spamcop.net/', '0000-00-00 00:00:00', '3', '0', 0, 0, 0, 0), 33 | ('bl.spameatingmonkey.net', 'ip', 'rbl', 'Listing entered by list reported by trusted users and IPs sending mail to a spamtraps. IPs from spam traps are automatically expired after 7 days of inactivity.', 'http://spameatingmonkey.com/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 34 | ('bl.tiopan.com', 'ip', 'rbl', 'Unknown - proprietary', 'http://www.tiopan.com/blacklist.php', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 35 | ('blackholes.five-ten-sg.com', 'ip', 'rbl', 'List has shut down.', 'http://www.five-ten-sg.com/blackhole.php', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 36 | ('blackholes.intersil.net', 'ip', 'rbl', 'List has shut down.', 'https://www.google.com/search?q=blackholes.intersil.net', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 37 | ('bogons.cymru.com', 'ip', 'rbl', 'Filters IPs that aren''t allocated by ARIN. Legitimately acquired ips will never show up on this list. There''s no point of monitoring it if you have valid IPs.', 'https://www.team-cymru.org/Services/Bogons/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 38 | ('cbl.abuseat.org', 'ip', 'rbl', 'Spam traps and administrator listings.', 'http://cbl.abuseat.org/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 39 | ('cbl.anti-spam.org.cn', 'ip', 'rbl', 'Chinese Anti-Spam Alliance blacklist service', 'http://www.anti-spam.org.cn/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 40 | ('combined.njabl.org', 'ip', 'rbl', 'List has shut down.', 'https://www.google.com/search?q=combined.njabl.org', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 41 | ('db.wpbl.info', 'ip', 'rbl', 'Fully automated. IPs automatically removed after a time.', 'http://wpbl.info/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 42 | ('dbl.spamhaus.org', 'domain', 'rbl', 'This mostly automated blacklist is usually activated by mailing invalid seed email addresses that are operated by spamhaus. Your domain will usually be automatically removed from this list unless you continue to email their spam traps. Then they will escalate the issue to larger blocks.', 'http://www.spamhaus.org/dbl/', '0000-00-00 00:00:00', '3', '1', 0, 0, 0, 0), 43 | ('dbl.tiopan.com', 'domain', 'rbl', 'Unknown - proprietary', 'http://www.tiopan.com/blacklist.php', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 44 | ('dnsbl-1.uceprotect.net', 'ip', 'rbl', 'Spamtraps and trusted sources. Only lists single ips.', 'http://www.uceprotect.net/en/index.php', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 45 | ('dnsbl-2.uceprotect.net', 'ip', 'rbl', 'Spamtraps and trusted sources. Will list large blocks of IPs that are connected.', 'http://www.uceprotect.net/en/index.php', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 46 | ('dnsbl-3.uceprotect.net', 'ip', 'rbl', 'Spamtraps and trusted sources. Will list entire ASN''s.', 'http://www.uceprotect.net/en/index.php', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 47 | ('dnsbl.ahbl.org', 'ip', 'rbl', 'List of IP that spammers own and use in their UCE/UBE/spam. They have several ways of identifying the domains manual and automated.', 'http://ahbl.org/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 48 | ('dnsbl.inps.de', 'ip', 'rbl', 'IP has sent email to seed email addresses.', 'http://dnsbl.inps.de/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 49 | ('dnsbl.justspam.org', 'ip', 'rbl', 'Claims to list only the worst offenders.', 'http://www.justspam.org/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 50 | ('dnsbl.sorbs.net', 'ip', 'rbl', 'Sorbs composite blocklist. Specializes in real time proxy and open relay checks.', 'http://www.sorbs.net/', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 51 | ('dnsblchile.org', 'ip', 'rbl', 'RBL run from Chile. If you have a large user base from Chile it may benefit you to monitor this list. Otherwise probably not.', 'http://www.dnsblchile.org/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 52 | ('dyna.spamrats.com', 'ip', 'rbl', 'RATS-Dyna is a collection of IP Addresses that have been found sending an abusive amount of connections, or trying too many invalid users at ISP and Telco''s mail servers, and are also known to conform to a naming convention that is indicative of a home connection or dynamic address space.', 'http://spamrats.com/rats-dyna.php', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 53 | ('ip.v4bl.org', 'ip', 'rbl', 'They do not allow commercial use. Unfortunately you''ll have to check this one manually.', 'http://v4bl.org/', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 54 | ('ips.backscatterer.org', 'ip', 'rbl', 'IPs that send misdirected bounces or autoresponders. Listing for 4 weeks.', 'http://www.backscatterer.org/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 55 | ('ix.dnsbl.manitu.net', 'ip', 'rbl', 'IPs that send to their seed address system.', 'http://www.dnsbl.manitu.net/', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 56 | ('l1.apews.org', 'domain', 'rbl', 'Apews automated system attempts to list domains of spammers before they start.', 'http://apews.org', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 57 | ('l2.apews.org', 'ip', 'rbl', 'Apews automated system attempts to list ips of spammers before they start.', 'http://www.apews.org/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 58 | ('list.anonwhois.net', 'domain', 'rbl', 'Checks for private registered domains. Private registration tends to count against delivery ability. There''s no point in checking if your own domains are privately registered. So this one has been disabled.', 'http://anonwhois.org/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 59 | ('multi.surbl.org', 'domain', 'rbl', 'Combined block list of domains known to be used in either spamming, phishing, or malware.', 'http://www.surbl.org/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 60 | ('multi.uribl.com', 'domain', 'rbl', 'Lists domains in emails.', 'http://www.uribl.com/index.shtml', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 61 | ('no-more-funn.moensted.dk', 'ip', 'rbl', 'List has shut down.', 'http://moensted.dk/spam/no-more-funn/', '0000-00-00 00:00:00', '1', '1', 1, 0, 0, 0), 62 | ('noptr.spamrats.com', 'ip', 'rbl', 'RATS-NoPtr is a collection of IP Addresses that have been found sending an abusive amount of connections, or trying too many invalid users at ISP and Telco''s mail servers, and are also known to have no reverse DNS, a technique often used by bots and spammers. Email servers should always have reverse DNS entries.', 'http://spamrats.com/rats-noptr.php', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 63 | ('psbl.surriel.com', 'ip', 'rbl', 'IPs pulled from their spam trap network. Fully automated.', 'http://psbl.org/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 64 | ('rbl.efnetrbl.org', 'ip', 'rbl', 'TOR nodes, open proxies, and spamtraps.', 'http://rbl.efnetrbl.org/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 65 | ('recent.spam.dnsbl.sorbs.net', 'ip', 'rbl', 'Sorbs recent list. IPs reported in the last 28 days.', 'http://www.sorbs.net/general/using.shtml', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 66 | ('spam.abuse.ch', 'ip', 'rbl', 'IPs that send to spamtraps.', 'http://www.abuse.ch/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 67 | ('spam.dnsbl.anonmails.de', 'ip', 'rbl', 'An anonymous mailbox provider. They don''t provide many details about their block list.', 'http://www.anonmails.de/dnsbl.php', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 68 | ('spam.dnsbl.sorbs.net', 'ip', 'rbl', 'Listings for hosts that aren''t taking actions.', 'http://www.sorbs.net/general/using.shtml', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 69 | ('spam.spamrats.com', 'ip', 'rbl', 'This is a list of IP Addresses that do not conform to more commonly known threats, and is usually because of compromised servers, hosts, or open relays. However, since there is little accompanying data this list COULD have false-positives, and we suggest that it only is used if you support a more aggressive stance.', 'http://spamrats.com/rats-spam.php', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 70 | ('spamguard.leadmon.net', 'ip', 'rbl', 'Small personally run RBL. Not used by any known ISPs.', 'http://www.leadmon.net/SpamGuard/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 71 | ('spamsources.fabel.dk', 'ip', 'rbl', 'Not much known. They only state that they will list your network when if send them spam.', 'http://www.spamsources.fabel.dk/', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 72 | ('t1.dnsbl.net.au', 'ip', 'rbl', 'Appears to be shutdown.', 'http://dnsbl.net.au/', '0000-00-00 00:00:00', '1', '0', 0, 0, 0, 0), 73 | ('tor.dnsbl.sectoor.de', 'ip', 'rbl', '', '', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 74 | ('torexit.dan.me.uk', 'ip', 'rbl', 'Contains all exit nodes on the TOR network.', 'https://www.dan.me.uk/dnsbl', '0000-00-00 00:00:00', '1', '1', 0, 0, 0, 0), 75 | ('truncate.gbudb.net', 'ip', 'rbl', 'Problem hosts as seen by Message Sniffer application', 'http://www.gbudb.com/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 76 | ('ubl.unsubscore.com', 'ip', 'rbl', 'Lashback''s monitoring for senders failing to provide a working or not honoring an unsubscribe mechanism.', 'http://blacklist.lashback.com/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 77 | ('urired.spameatingmonkey.net', 'domain', 'rbl', 'Domains found in unwanted emails and preemptive technology. Automatically expires after 30 days of inactivity.', 'http://spameatingmonkey.com/', '0000-00-00 00:00:00', '2', '1', 0, 0, 0, 0), 78 | ('virbl.dnsbl.bit.nl', 'ip', 'rbl', '', '', '0000-00-00 00:00:00', '2', '0', 0, 0, 0, 0), 79 | ('zen.spamhaus.org', 'ip', 'rbl', 'Composite host block list of all spamhaus ips.', 'http://www.spamhaus.org/zen/', '0000-00-00 00:00:00', '3', '1', 0, 0, 0, 0); 80 | 81 | -- 82 | -- Table structure for table `monitorHistory` 83 | -- 84 | 85 | DROP TABLE IF EXISTS `monitorHistory`; 86 | CREATE TABLE IF NOT EXISTS `monitorHistory` ( 87 | `monitorTime` datetime NOT NULL, 88 | `isBlocked` tinyint(1) NOT NULL DEFAULT '0', 89 | `ipDomain` varchar(100) NOT NULL, 90 | `rDNS` varchar(200) NOT NULL, 91 | `status` text NOT NULL 92 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 93 | 94 | 95 | -- 96 | -- Table structure for table `monitors` 97 | -- 98 | 99 | DROP TABLE IF EXISTS `monitors`; 100 | CREATE TABLE IF NOT EXISTS `monitors` ( 101 | `ipDomain` varchar(100) NOT NULL, 102 | `isDomain` tinyint(1) NOT NULL DEFAULT '0', 103 | `beenChecked` tinyint(1) NOT NULL DEFAULT '0', 104 | `lastUpdate` datetime NOT NULL, 105 | `isBlocked` tinyint(1) NOT NULL DEFAULT '0', 106 | `lastStatusChanged` tinyint(1) NOT NULL DEFAULT '0', 107 | `monitorGroupId` int(11) NOT NULL DEFAULT '0', 108 | `keepOnUpdate` tinyint(1) NOT NULL DEFAULT '1', 109 | `lastStatusChangeTime` datetime NOT NULL, 110 | `rDNS` varchar(200) NOT NULL, 111 | `status` text NOT NULL 112 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 113 | 114 | -- 115 | -- Table structure for table `users` 116 | -- 117 | 118 | DROP TABLE IF EXISTS `users`; 119 | CREATE TABLE IF NOT EXISTS `users` ( 120 | `username` varchar(100) NOT NULL, 121 | `passwd` varchar(32) NOT NULL, 122 | `apiKey` varchar(32) NOT NULL, 123 | `lastUpdate` datetime NOT NULL, 124 | `lastChecked` datetime NOT NULL, 125 | `beenChecked` tinyint(1) NOT NULL DEFAULT '0', 126 | `lastRunTime` int(11) NOT NULL DEFAULT '0', 127 | `disableEmailNotices` tinyint(1) NOT NULL DEFAULT '0', 128 | `twitterHandle` varchar(15) NOT NULL, 129 | `checkFrequency` enum('1hour','2hour','8hour','daily','weekly') NOT NULL DEFAULT 'daily', 130 | `noticeEmailAddresses` varchar(8000) NOT NULL, 131 | `textMessageEmails` varchar(8000) NOT NULL, 132 | `apiCallbackURL` varchar(2000) NOT NULL, 133 | `ips` LONGTEXT NOT NULL, 134 | `domains` LONGTEXT NOT NULL 135 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 136 | 137 | 138 | INSERT INTO `users` (`username`, `passwd`, `apiKey`, `lastUpdate`, `lastChecked`, `beenChecked`, `lastRunTime`, `disableEmailNotices`, `twitterHandle`, `checkFrequency`, `noticeEmailAddresses`, `textMessageEmails`, `apiCallbackURL`, `ips`, `domains`) VALUES 139 | ('admin', '97bf34d31a8710e6b1649fd33357f783', '', '2015-06-02 16:37:02', '2015-06-02 16:22:55', 1, 4, 0, '', '2hour', '', '', '', '', ''); 140 | 141 | 142 | DROP TABLE IF EXISTS `monitorGroup`; 143 | CREATE TABLE IF NOT EXISTS `monitorGroup` ( 144 | `id` int(11) NOT NULL, 145 | `groupName` varchar(100) NOT NULL, 146 | `ips` longtext NOT NULL, 147 | `domains` longtext NOT NULL 148 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 149 | 150 | -- 151 | -- Indexes for table `blockLists` 152 | -- 153 | ALTER TABLE `blockLists` 154 | ADD PRIMARY KEY (`host`), 155 | ADD KEY `monitorType` (`monitorType`), 156 | ADD KEY `isActive` (`isActive`); 157 | 158 | -- 159 | -- Indexes for table `monitorHistory` 160 | -- 161 | ALTER TABLE `monitorHistory` 162 | ADD KEY `monitorTime` (`monitorTime`), 163 | ADD KEY `ipDomain` (`ipDomain`); 164 | 165 | -- 166 | -- Indexes for table `monitors` 167 | -- 168 | ALTER TABLE `monitors` 169 | ADD PRIMARY KEY (`ipDomain`), 170 | ADD KEY `beenChecked` (`beenChecked`), 171 | ADD KEY `isBlocked` (`isBlocked`), 172 | ADD KEY `lastUpdate` (`lastUpdate`), 173 | ADD KEY `isDomain` (`isDomain`), 174 | ADD KEY `monitorGroupId` (`monitorGroupId`); 175 | 176 | -- 177 | -- Indexes for table `users` 178 | -- 179 | ALTER TABLE `users` 180 | ADD PRIMARY KEY (`username`); 181 | 182 | -- 183 | -- Indexes for table `monitorGroup` 184 | -- 185 | ALTER TABLE `monitorGroup` 186 | ADD PRIMARY KEY (`id`), 187 | ADD UNIQUE KEY `groupName` (`groupName`); 188 | 189 | ALTER TABLE `monitorGroup` 190 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 191 | -------------------------------------------------------------------------------- /js/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.extend({tablesorter:new 3 | function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;iget($cacheKey); 19 | if ($cacheData !== false) { 20 | if(isset($cacheData['domains']) && isset($cacheData['ips']) ) { 21 | self::$domainBlacklists = $cacheData['domains']; 22 | self::$ipBlacklists = $cacheData['ips']; 23 | return true; 24 | } 25 | } 26 | $mysql = new _MySQL(); 27 | $mysql->connect(Setup::$connectionArray); 28 | $sql = "select host,monitorType from blockLists where isActive = '1';"; 29 | $rs = $mysql->runQuery($sql); 30 | $cacheData['domains'] = array(); 31 | $cacheData['ips'] = array(); 32 | while($row = mysqli_fetch_array($rs)){ 33 | if($row['monitorType']=='ip'){ 34 | $cacheData['ips'][] = $row['host']; 35 | }else{ 36 | $cacheData['domains'][] = $row['host']; 37 | } 38 | } 39 | $mysql->close(); 40 | $localCache->set($cacheKey, $cacheData); 41 | self::$domainBlacklists = $cacheData['domains']; 42 | self::$ipBlacklists = $cacheData['ips']; 43 | return false; 44 | } 45 | 46 | public static function randomDNSServer(){ 47 | return Setup::$settings['dns_servers'][mt_rand(0,(count(Setup::$settings['dns_servers'])-1))]; 48 | } 49 | 50 | public static $isBlocked = 0; 51 | 52 | public static function checkBlacklists($domainOrIp, $reportClean=false){ 53 | self::$isBlocked = 0; 54 | $return = array(); 55 | if(_IpAddresses::isIPAddress($domainOrIp)){ 56 | foreach(self::$ipBlacklists as $server){ 57 | $r = self::ipCheck($domainOrIp, $server); 58 | if($r!='') { 59 | self::$isBlocked = 1; 60 | self::logBlockListStats($server, 'ip', true); 61 | }else{ 62 | self::logBlockListStats($server, 'ip', false); 63 | } 64 | if($r!='' || $reportClean==true) { 65 | $return[] = array(trim($server),$r); 66 | } 67 | } 68 | }else{ 69 | foreach(self::$domainBlacklists as $server){ 70 | $r = self::domainCheck($domainOrIp, $server); 71 | if($r!='') { 72 | self::$isBlocked = 1; 73 | self::logBlockListStats($server, 'domain', true); 74 | }else{ 75 | self::logBlockListStats($server, 'domain', false); 76 | } 77 | if($r!='' || $reportClean==true) { 78 | $return[] = array(trim($server),$r); 79 | } 80 | } 81 | } 82 | return $return; 83 | } 84 | 85 | public static function domainCheck($domain, $server){ 86 | $server = trim($server); 87 | $host = escapeshellarg("$domain.$server"); 88 | $t = "dig @".self::randomDNSServer()." +time=".Setup::$settings['dns_request_timeout']." $host"; 89 | // echo("$t
"); 90 | $text = shell_exec($t); 91 | $test = Utilities::parseBetweenText( 92 | $text, 93 | ";; ANSWER SECTION:\n", 94 | "\n\n", 95 | false, 96 | false, 97 | true); 98 | $testArray = explode("\t", $test); 99 | $test = end($testArray); 100 | 101 | if(trim($test)!=''){ 102 | if(Setup::$settings['rbl_txt_extended_status']){ 103 | $t = "dig @".self::randomDNSServer()." +time=".Setup::$settings['dns_request_timeout']." $host txt"; 104 | // echo("$t
"); 105 | $text = shell_exec($t); 106 | $test = Utilities::parseBetweenText( 107 | $text, 108 | ";; ANSWER SECTION:\n", 109 | "\n\n", 110 | false, 111 | false, 112 | true); 113 | $testArray = explode("\t", $test); 114 | $test = end($testArray); 115 | $test = str_replace(array('\'','"'),'',$test); 116 | }else{ 117 | $test = 'blocked'; 118 | } 119 | } 120 | if(strripos($test,'not found')!==false) return ''; 121 | if(strripos($test,'SERVFAIL')!==false) return ''; 122 | return trim($test); 123 | } 124 | 125 | public static function ipCheck($ip, $server){ 126 | if(_IpAddresses::isIPAddress($ip)===false) return ''; 127 | $server = trim($server); 128 | 129 | $parts = explode('.', $ip); 130 | $reverseIp = implode('.', array_reverse($parts)); 131 | $text = ""; 132 | $host = escapeshellarg("$reverseIp.$server"); 133 | $t = "dig @".self::randomDNSServer()." +time=".Setup::$settings['dns_request_timeout']." $host"; 134 | // echo("$t
"); 135 | $text = shell_exec($t); 136 | $test = Utilities::parseBetweenText( 137 | $text, 138 | ";; ANSWER SECTION:\n", 139 | "\n\n", 140 | false, 141 | false, 142 | true); 143 | $testArray = preg_split("/IN\s+A\s+/i", $test); 144 | $test = trim(end($testArray)); 145 | // echo "
$test
\n"; 146 | if(trim($test)!=''){ 147 | if(Setup::$settings['rbl_txt_extended_status']){ 148 | $t = "dig @".self::randomDNSServer()." +time=".Setup::$settings['dns_request_timeout']." $host txt"; 149 | // echo("$t
"); 150 | $text = shell_exec($t); 151 | $test2 = Utilities::parseBetweenText( 152 | $text, 153 | ";; ANSWER SECTION:\n", 154 | "\n\n", 155 | false, 156 | false, 157 | true); 158 | $testArray = preg_split("/IN\s+TXT\s+/i", $test2); 159 | $test2 = trim(end($testArray)); 160 | $test2 = str_replace(array('\'','"'),'',$test2); 161 | switch($server){ 162 | case 'bl.mailspike.net': 163 | $a = explode("|",$test2); 164 | $test = (isset($a[1])) ? 'Listed ' . $a[1] : $test2; 165 | break; 166 | } 167 | if($test2!='') $test = $test2; 168 | }else{ 169 | $test = 'blocked'; 170 | } 171 | } 172 | if(strripos($test,'not found')!==false) return ''; 173 | if(strripos($test,'SERVFAIL')!==false) return ''; 174 | return trim($test); 175 | } 176 | 177 | public static function logBlockListStats($server, $monitorType, $isBlocked){ 178 | if(Setup::$settings['log_rbl_stats']==0) return true; 179 | $mysql = new _MySQL(); 180 | $mysql->connect(Setup::$connectionArray); 181 | if($isBlocked){ 182 | $sql = "update blockLists set blocksToday=(blocksToday+1), lastBlockReport=now() where host = '".$mysql->escape($server)."' and monitorType = '$monitorType';"; 183 | }else{ 184 | $sql = "update blockLists set cleanToday=(cleanToday+1) where host = '".$mysql->escape($server)."' and monitorType = '$monitorType';"; 185 | } 186 | $mysql->runQuery($sql); 187 | $mysql->close(); 188 | } 189 | 190 | public static function ensureGroupExists($groupName){ 191 | $mysql = new _MySQL(); 192 | $mysql->connect(Setup::$connectionArray); 193 | $id = $mysql->runQueryReturnVar("select id from monitorGroup where groupName = '".$mysql->escape($groupName)."'"); 194 | if($id===false){ 195 | $mysql->runQuery("insert into monitorGroup set groupName = '".$mysql->escape($groupName)."'"); 196 | $id = $mysql->identity; 197 | } 198 | $mysql->close(); 199 | return $id; 200 | } 201 | 202 | public static function updateDomains($domains, $monitorGroupId){ 203 | $domains = trim($domains); 204 | $monitorGroupId = (int)$monitorGroupId; 205 | if($monitorGroupId===0) return false; 206 | $mysql = new _MySQL(); 207 | $mysql->connect(Setup::$connectionArray); 208 | $mysql->runQuery("update monitors set keepOnUpdate = 0 where isDomain = 1 and monitorGroupId = $monitorGroupId"); 209 | $mysql->runQuery("update users set lastUpdate = '".$mysql->escape(date('Y-m-d H:i:s'))."'"); 210 | $mysql->runQuery("update monitorGroup set domains = '".$mysql->escape($domains)."' where id = $monitorGroupId"); 211 | $domainArray = preg_split('/\s+/', $domains); 212 | foreach($domainArray as $d){ 213 | $d = trim($d); 214 | $d = str_ireplace('http://', '', $d); 215 | $d = str_ireplace('https://', '', $d); 216 | $d = str_ireplace('/', '', $d); 217 | $d = preg_replace('/[[:^print:]]/', '', $d); 218 | if($d != ''){ 219 | $mysql->runQuery(" 220 | update monitors set 221 | keepOnUpdate = 1 222 | where 223 | monitorGroupId = $monitorGroupId 224 | and ipDomain = '".$mysql->escape($d)."' 225 | and isDomain = 1 226 | "); 227 | if($mysql->affectedRows == 0){ 228 | $mysql->runQuery("insert ignore into monitors set 229 | monitorGroupId = $monitorGroupId, 230 | ipDomain = '".$mysql->escape($d)."', 231 | isDomain = 1, 232 | keepOnUpdate = 1 233 | "); 234 | } 235 | } 236 | } 237 | $mysql->runQuery("delete from monitors where keepOnUpdate = 0 and isDomain = 1 and monitorGroupId = $monitorGroupId"); 238 | $mysql->close(); 239 | } 240 | 241 | public static function updateIPs($ips, $monitorGroupId){ 242 | $ips = trim($ips); 243 | $monitorGroupId = (int)$monitorGroupId; 244 | if($monitorGroupId===0) return false; 245 | $mysql = new _MySQL(); 246 | $mysql->connect(Setup::$connectionArray); 247 | $mysql->runQuery("update monitors set keepOnUpdate = 0 where isDomain = 0 and monitorGroupId = $monitorGroupId"); 248 | $mysql->runQuery("update users set lastUpdate = '".$mysql->escape(date('Y-m-d H:i:s'))."'"); 249 | $mysql->runQuery("update monitorGroup set ips = '".$mysql->escape($ips)."' where id = $monitorGroupId"); 250 | $ipsArray = preg_split('/\s+/', $ips); 251 | foreach($ipsArray as $i){ 252 | // ip checks 253 | if(_IpAddresses::isIPAddress($i)){ 254 | $mysql->runQuery(" 255 | update monitors set 256 | keepOnUpdate = 1 257 | where 258 | monitorGroupId = $monitorGroupId 259 | and ipDomain = '".$mysql->escape($i)."' 260 | and isDomain = 0 261 | "); 262 | if($mysql->affectedRows == 0){ 263 | $mysql->runQuery("insert ignore into monitors set 264 | monitorGroupId = $monitorGroupId, 265 | ipDomain = '".$mysql->escape($i)."', 266 | isDomain = 0, 267 | keepOnUpdate = 1 268 | "); 269 | } 270 | }else{ 271 | //cidr /24's max... 272 | if(trim($i)!=''){ 273 | if(strpos($i, ' ')!==false) continue; 274 | if(strpos($i, ':')!==false) continue; 275 | $range = _IpAddresses::cidrToRange($i); 276 | if($range===false) continue; 277 | $start = explode('.', $range[0]); 278 | $end = explode('.', $range[1]); 279 | if($range[0]==0) continue;// starts with 0 280 | for($i = $start[3]; $i <= $end[3]; $i++){ 281 | $host = "{$start[0]}.{$start[1]}.{$start[2]}.$i"; 282 | if(_IpAddresses::isIPAddress($host)){ 283 | $mysql->runQuery(" 284 | update monitors set 285 | keepOnUpdate = 1 286 | where 287 | monitorGroupId = $monitorGroupId 288 | and ipDomain = '".$mysql->escape($host)."' 289 | and isDomain = 0 290 | "); 291 | if($mysql->affectedRows == 0){ 292 | $mysql->runQuery("insert ignore into monitors set 293 | monitorGroupId = $monitorGroupId, 294 | ipDomain = '".$mysql->escape($host)."', 295 | isDomain = 0, 296 | keepOnUpdate = 1 297 | "); 298 | } 299 | } 300 | } 301 | } 302 | } 303 | } 304 | $mysql->runQuery("delete from monitors where keepOnUpdate = 0 and isDomain = 0 and monitorGroupId = $monitorGroupId"); 305 | $mysql->close(); 306 | } 307 | 308 | public static function isLoggedIn(){ 309 | if(isset($_SESSION['id']) && (int)$_SESSION['id'] > 0){ 310 | return $_SESSION['id']; 311 | }else{ 312 | return false; 313 | } 314 | } 315 | 316 | public static function getAccount(){ 317 | $mysql = new _MySQL(); 318 | $mysql->connect(Setup::$connectionArray); 319 | $ret = false; 320 | $rs = $mysql->runQuery(" 321 | select 322 | username, 323 | passwd, 324 | apiKey, 325 | beenChecked, 326 | disableEmailNotices, 327 | noticeEmailAddresses, 328 | textMessageEmails, 329 | twitterHandle, 330 | apiCallbackURL, 331 | checkFrequency 332 | from users limit 1"); 333 | while($row = mysqli_fetch_array($rs)){ 334 | $ret = $row; 335 | } 336 | $mysql->close(); 337 | 338 | if(!$ret){ 339 | //account 340 | _Logging::appLog("no user account"); 341 | exit(); 342 | } 343 | 344 | return $ret; 345 | } 346 | 347 | public static function validateLogin($userName, $passwd, $api = false, $apiKey = ''){ 348 | $mysql = new _MySQL(); 349 | $mysql->connect(Setup::$connectionArray); 350 | $sql = " 351 | select username 352 | from users 353 | where "; 354 | if(trim($apiKey) != ''){ 355 | $sql .= " apiKey = '".$mysql->escape($apiKey)."'"; 356 | }else{ 357 | $sql .= " passwd = '".$mysql->escape(md5($passwd))."' 358 | and username = '".$mysql->escape($userName)."'"; 359 | } 360 | $rs = $mysql->runQuery($sql); 361 | $id = 0; 362 | while($row = mysqli_fetch_array($rs)){ 363 | $id = 1; 364 | } 365 | $mysql->close(); 366 | return $id; 367 | } 368 | 369 | public static function lookupHostDNS($host){ 370 | if(_IpAddresses::isIPAddress($host)){ 371 | return _IpAddresses::getHostByIp($host); 372 | }else{ 373 | $host = escapeshellarg($host); 374 | exec('host -t a -W 2 '.$host, $output, $return); 375 | if ($return !== 0) { 376 | return ''; 377 | }else{ 378 | $output = implode($output); 379 | $ips = _IpAddresses::getAllIPsFromString($output, true); 380 | $ir = ""; 381 | foreach($ips as $ip){ 382 | $ir .= "$ip,"; 383 | } 384 | return trim($ir,','); 385 | } 386 | 387 | /* 388 | if(strripos($host,'not found')!==false) return false; 389 | if(strripos($host,'SERVFAIL')!==false) return false; 390 | $phost = trim(end(explode(' ', $host))); 391 | if(strripos($phost,'reached')!==false) return false; 392 | return $phost; 393 | */ 394 | } 395 | } 396 | 397 | public static function testAPICallback($url){ 398 | return self::makeAPICallback($url, 399 | 'samplehosttest.com', 400 | true, 401 | 'reverse-dns-sample.samplehosttest.com', 402 | 'a:2:{i:0;a:2:{i:0;s:12:"l2.apews.org";i:1;s:87:"Listed at APEWS-L2 - visit http://www.apews.org/?page=test&C=131&E=1402188&ip=127.0.0.1";}i:1;a:2:{i:0;s:22:"b.barracudacentral.org";i:1;s:9:"127.0.0.2";}}' 403 | ); 404 | } 405 | 406 | public static function makeAPICallback($url, $host, $isBlocked, $rDNS, $status){ 407 | if(substr($url,0,4)!='http') return false; 408 | 409 | $vars = json_encode( 410 | array( 411 | 'host'=>$host, 412 | 'isBlocked'=>(boolean)$isBlocked, 413 | 'rDNS'=>$rDNS, 414 | 'blocks'=>unserialize($status) 415 | ) 416 | ); 417 | $err = true; 418 | try{ 419 | $ch = curl_init(); 420 | curl_setopt($ch,CURLOPT_URL,$url); 421 | curl_setopt($ch,CURLOPT_POST,true); 422 | curl_setopt($ch,CURLOPT_FAILONERROR,true); 423 | curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); 424 | curl_setopt($ch,CURLOPT_POSTFIELDS,$vars); 425 | curl_setopt($ch,CURLOPT_HTTPHEADER, array('Content-Type: application/json')); 426 | curl_exec($ch); 427 | if (curl_errno($ch)) $err = false; 428 | } catch (Exception $e) { 429 | $err = false; 430 | } 431 | return $err; 432 | } 433 | 434 | public static function isValidEmail($emailAddress){ 435 | if (filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) { 436 | return true; 437 | } 438 | return false; 439 | } 440 | 441 | public static function parseBetweenText( 442 | $text, 443 | $beginText, 444 | $endText, 445 | $removeSpace=true, 446 | $removeHtmlTags=true, 447 | $firstResultOnlyNoArray=false) { 448 | $results = array(); 449 | $endPos = 0; 450 | while(true) { 451 | $beginPos = stripos($text, $beginText, $endPos); 452 | if($beginPos===false) break; 453 | $beginPos = $beginPos+strlen($beginText); 454 | $endPos = stripos($text, $endText, $beginPos); 455 | if($endPos===false) break; 456 | $result = substr($text, $beginPos, $endPos-$beginPos); 457 | if($removeSpace){ 458 | $result = str_replace("\t","",$result); 459 | $result = str_replace("\n","",$result); 460 | $result = preg_replace("/ /"," ",$result); 461 | $result = preg_replace("~[\s]{2}?[\t]?~i"," ",$result); 462 | $result = str_replace(" "," ",$result); 463 | $result = trim($result); 464 | } 465 | if($removeHtmlTags){ 466 | $result = strip_tags($result); 467 | } 468 | if($firstResultOnlyNoArray) return $result; 469 | if($result != '') $results[] = $result; 470 | } 471 | return ($firstResultOnlyNoArray && empty($results) ? '' : $results); 472 | } 473 | 474 | public static function getNextMonitor($mysql){ 475 | $ipDomain = $mysql->runQueryReturnVar("select ipDomain from monitors where beenChecked = 0"); 476 | $mysql->runQuery("update monitors set beenChecked = 1 where ipDomain = '".$mysql->escape($ipDomain)."'"); 477 | return $ipDomain; 478 | } 479 | 480 | public static function getHostChangeCount($mysql, $monitorGroupId = 0) { 481 | $sql = ''; 482 | $monitorGroupId = (int)$monitorGroupId; 483 | if($monitorGroupId > 0) $sql = " and monitorGroupId = $monitorGroupId"; 484 | return $mysql->runQueryReturnVar("select COALESCE(count(ipDomain),0) as cnt from monitors where lastStatusChanged = 1 $sql"); 485 | } 486 | 487 | public static function getHostErrorCount($mysql, $monitorGroupId = 0, $onlyNew = false) { 488 | $sql = ''; 489 | $monitorGroupId = (int)$monitorGroupId; 490 | if($monitorGroupId > 0) $sql = " and monitorGroupId = $monitorGroupId"; 491 | if($onlyNew) $sql .= " and lastStatusChanged = 1 "; 492 | return $mysql->runQueryReturnVar("select COALESCE(count(ipDomain),0) as cnt from monitors where isBlocked = 1 $sql"); 493 | } 494 | 495 | public static function getHostCleanCount($mysql, $monitorGroupId = 0, $onlyNew = false) { 496 | $sql = ''; 497 | $monitorGroupId = (int)$monitorGroupId; 498 | if($monitorGroupId > 0) $sql = " and monitorGroupId = $monitorGroupId"; 499 | if($onlyNew) $sql .= " and lastStatusChanged = 1 "; 500 | return $mysql->runQueryReturnVar("select COALESCE(count(ipDomain),0) as cnt from monitors where isBlocked = 0 $sql"); 501 | } 502 | 503 | public static function getHostCount($mysql, $monitorGroupId = 0) { 504 | $sql = ''; 505 | $monitorGroupId = (int)$monitorGroupId; 506 | if($monitorGroupId > 0) $sql = " where monitorGroupId = $monitorGroupId"; 507 | return $mysql->runQueryReturnVar("select COALESCE(count(ipDomain),0) as cnt from monitors $sql"); 508 | } 509 | 510 | //CREDIT: http://braincrafted.com/php-background-processes/ 511 | public static function is_process_running($pid){ 512 | $pid = (int)$pid; 513 | if($pid == 0) return false; 514 | if(file_exists('/proc/'.$pid)){ 515 | return true; 516 | }else{ 517 | return false; 518 | } 519 | } 520 | 521 | public static function run_in_background($command, $priority = 0) { 522 | $log = Setup::$settings['log_path']; 523 | if($priority !=0){ 524 | $pid = shell_exec("nohup nice -n $priority $command >> $log 2>&1 & echo $!"); 525 | }else{ 526 | $pid = shell_exec("nohup $command >> $log 2>&1 & echo $!"); 527 | } 528 | return($pid); 529 | } 530 | 531 | } 532 | 533 | 534 | 535 | 536 | 537 | -------------------------------------------------------------------------------- /classes/class.smtp.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer RFC821 SMTP email transport class. 22 | * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. 23 | * @package PHPMailer 24 | * @author Chris Ryan 25 | * @author Marcus Bointon 26 | */ 27 | class SMTP 28 | { 29 | /** 30 | * The PHPMailer SMTP version number. 31 | * @type string 32 | */ 33 | const VERSION = '5.2.10'; 34 | 35 | /** 36 | * SMTP line break constant. 37 | * @type string 38 | */ 39 | const CRLF = "\r\n"; 40 | 41 | /** 42 | * The SMTP port to use if one is not specified. 43 | * @type integer 44 | */ 45 | const DEFAULT_SMTP_PORT = 25; 46 | 47 | /** 48 | * The maximum line length allowed by RFC 2822 section 2.1.1 49 | * @type integer 50 | */ 51 | const MAX_LINE_LENGTH = 998; 52 | 53 | /** 54 | * Debug level for no output 55 | */ 56 | const DEBUG_OFF = 0; 57 | 58 | /** 59 | * Debug level to show client -> server messages 60 | */ 61 | const DEBUG_CLIENT = 1; 62 | 63 | /** 64 | * Debug level to show client -> server and server -> client messages 65 | */ 66 | const DEBUG_SERVER = 2; 67 | 68 | /** 69 | * Debug level to show connection status, client -> server and server -> client messages 70 | */ 71 | const DEBUG_CONNECTION = 3; 72 | 73 | /** 74 | * Debug level to show all messages 75 | */ 76 | const DEBUG_LOWLEVEL = 4; 77 | 78 | /** 79 | * The PHPMailer SMTP Version number. 80 | * @type string 81 | * @deprecated Use the `VERSION` constant instead 82 | * @see SMTP::VERSION 83 | */ 84 | public $Version = '5.2.10'; 85 | 86 | /** 87 | * SMTP server port number. 88 | * @type integer 89 | * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead 90 | * @see SMTP::DEFAULT_SMTP_PORT 91 | */ 92 | public $SMTP_PORT = 25; 93 | 94 | /** 95 | * SMTP reply line ending. 96 | * @type string 97 | * @deprecated Use the `CRLF` constant instead 98 | * @see SMTP::CRLF 99 | */ 100 | public $CRLF = "\r\n"; 101 | 102 | /** 103 | * Debug output level. 104 | * Options: 105 | * * self::DEBUG_OFF (`0`) No debug output, default 106 | * * self::DEBUG_CLIENT (`1`) Client commands 107 | * * self::DEBUG_SERVER (`2`) Client commands and server responses 108 | * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status 109 | * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages 110 | * @type integer 111 | */ 112 | public $do_debug = self::DEBUG_OFF; 113 | 114 | /** 115 | * How to handle debug output. 116 | * Options: 117 | * * `echo` Output plain-text as-is, appropriate for CLI 118 | * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output 119 | * * `error_log` Output to error log as configured in php.ini 120 | * 121 | * Alternatively, you can provide a callable expecting two params: a message string and the debug level: 122 | * 123 | * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; 124 | * 125 | * @type string|callable 126 | */ 127 | public $Debugoutput = 'echo'; 128 | 129 | /** 130 | * Whether to use VERP. 131 | * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path 132 | * @link http://www.postfix.org/VERP_README.html Info on VERP 133 | * @type boolean 134 | */ 135 | public $do_verp = false; 136 | 137 | /** 138 | * The timeout value for connection, in seconds. 139 | * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 140 | * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. 141 | * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 142 | * @type integer 143 | */ 144 | public $Timeout = 300; 145 | 146 | /** 147 | * How long to wait for commands to complete, in seconds. 148 | * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 149 | * @type integer 150 | */ 151 | public $Timelimit = 300; 152 | 153 | /** 154 | * The socket for the server connection. 155 | * @type resource 156 | */ 157 | protected $smtp_conn; 158 | 159 | /** 160 | * Error information, if any, for the last SMTP command. 161 | * @type array 162 | */ 163 | protected $error = array( 164 | 'error' => '', 165 | 'detail' => '', 166 | 'smtp_code' => '', 167 | 'smtp_code_ex' => '' 168 | ); 169 | 170 | /** 171 | * The reply the server sent to us for HELO. 172 | * If null, no HELO string has yet been received. 173 | * @type string|null 174 | */ 175 | protected $helo_rply = null; 176 | 177 | /** 178 | * The set of SMTP extensions sent in reply to EHLO command. 179 | * Indexes of the array are extension names. 180 | * Value at index 'HELO' or 'EHLO' (according to command that was sent) 181 | * represents the server name. In case of HELO it is the only element of the array. 182 | * Other values can be boolean TRUE or an array containing extension options. 183 | * If null, no HELO/EHLO string has yet been received. 184 | * @type array|null 185 | */ 186 | protected $server_caps = null; 187 | 188 | /** 189 | * The most recent reply received from the server. 190 | * @type string 191 | */ 192 | protected $last_reply = ''; 193 | 194 | /** 195 | * Output debugging info via a user-selected method. 196 | * @see SMTP::$Debugoutput 197 | * @see SMTP::$do_debug 198 | * @param string $str Debug string to output 199 | * @param integer $level The debug level of this message; see DEBUG_* constants 200 | * @return void 201 | */ 202 | protected function edebug($str, $level = 0) 203 | { 204 | if ($level > $this->do_debug) { 205 | return; 206 | } 207 | //Avoid clash with built-in function names 208 | if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { 209 | call_user_func($this->Debugoutput, $str, $this->do_debug); 210 | return; 211 | } 212 | switch ($this->Debugoutput) { 213 | case 'error_log': 214 | //Don't output, just log 215 | error_log($str); 216 | break; 217 | case 'html': 218 | //Cleans up output a bit for a better looking, HTML-safe output 219 | echo htmlentities( 220 | preg_replace('/[\r\n]+/', '', $str), 221 | ENT_QUOTES, 222 | 'UTF-8' 223 | ) 224 | . "
\n"; 225 | break; 226 | case 'echo': 227 | default: 228 | //Normalize line breaks 229 | $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); 230 | echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( 231 | "\n", 232 | "\n \t ", 233 | trim($str) 234 | )."\n"; 235 | } 236 | } 237 | 238 | /** 239 | * Connect to an SMTP server. 240 | * @param string $host SMTP server IP or host name 241 | * @param integer $port The port number to connect to 242 | * @param integer $timeout How long to wait for the connection to open 243 | * @param array $options An array of options for stream_context_create() 244 | * @access public 245 | * @return boolean 246 | */ 247 | public function connect($host, $port = null, $timeout = 30, $options = array()) 248 | { 249 | static $streamok; 250 | //This is enabled by default since 5.0.0 but some providers disable it 251 | //Check this once and cache the result 252 | if (is_null($streamok)) { 253 | $streamok = function_exists('stream_socket_client'); 254 | } 255 | // Clear errors to avoid confusion 256 | $this->setError(''); 257 | // Make sure we are __not__ connected 258 | if ($this->connected()) { 259 | // Already connected, generate error 260 | $this->setError('Already connected to a server'); 261 | return false; 262 | } 263 | if (empty($port)) { 264 | $port = self::DEFAULT_SMTP_PORT; 265 | } 266 | // Connect to the SMTP server 267 | $this->edebug( 268 | "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), 269 | self::DEBUG_CONNECTION 270 | ); 271 | $errno = 0; 272 | $errstr = ''; 273 | if ($streamok) { 274 | $socket_context = stream_context_create($options); 275 | //Suppress errors; connection failures are handled at a higher level 276 | $this->smtp_conn = @stream_socket_client( 277 | $host . ":" . $port, 278 | $errno, 279 | $errstr, 280 | $timeout, 281 | STREAM_CLIENT_CONNECT, 282 | $socket_context 283 | ); 284 | } else { 285 | //Fall back to fsockopen which should work in more places, but is missing some features 286 | $this->edebug( 287 | "Connection: stream_socket_client not available, falling back to fsockopen", 288 | self::DEBUG_CONNECTION 289 | ); 290 | $this->smtp_conn = fsockopen( 291 | $host, 292 | $port, 293 | $errno, 294 | $errstr, 295 | $timeout 296 | ); 297 | } 298 | // Verify we connected properly 299 | if (!is_resource($this->smtp_conn)) { 300 | $this->setError( 301 | 'Failed to connect to server', 302 | $errno, 303 | $errstr 304 | ); 305 | $this->edebug( 306 | 'SMTP ERROR: ' . $this->error['error'] 307 | . ": $errstr ($errno)", 308 | self::DEBUG_CLIENT 309 | ); 310 | return false; 311 | } 312 | $this->edebug('Connection: opened', self::DEBUG_CONNECTION); 313 | // SMTP server can take longer to respond, give longer timeout for first read 314 | // Windows does not have support for this timeout function 315 | if (substr(PHP_OS, 0, 3) != 'WIN') { 316 | $max = ini_get('max_execution_time'); 317 | // Don't bother if unlimited 318 | if ($max != 0 && $timeout > $max) { 319 | @set_time_limit($timeout); 320 | } 321 | stream_set_timeout($this->smtp_conn, $timeout, 0); 322 | } 323 | // Get any announcement 324 | $announce = $this->get_lines(); 325 | $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); 326 | return true; 327 | } 328 | 329 | /** 330 | * Initiate a TLS (encrypted) session. 331 | * @access public 332 | * @return boolean 333 | */ 334 | public function startTLS() 335 | { 336 | if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { 337 | return false; 338 | } 339 | // Begin encrypted connection 340 | if (!stream_socket_enable_crypto( 341 | $this->smtp_conn, 342 | true, 343 | STREAM_CRYPTO_METHOD_TLS_CLIENT 344 | )) { 345 | return false; 346 | } 347 | return true; 348 | } 349 | 350 | /** 351 | * Perform SMTP authentication. 352 | * Must be run after hello(). 353 | * @see hello() 354 | * @param string $username The user name 355 | * @param string $password The password 356 | * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5) 357 | * @param string $realm The auth realm for NTLM 358 | * @param string $workstation The auth workstation for NTLM 359 | * @access public 360 | * @return boolean True if successfully authenticated. 361 | */ 362 | public function authenticate( 363 | $username, 364 | $password, 365 | $authtype = null, 366 | $realm = '', 367 | $workstation = '' 368 | ) { 369 | if (!$this->server_caps) { 370 | $this->setError('Authentication is not allowed before HELO/EHLO'); 371 | return false; 372 | } 373 | 374 | if (array_key_exists('EHLO', $this->server_caps)) { 375 | // SMTP extensions are available. Let's try to find a proper authentication method 376 | 377 | if (!array_key_exists('AUTH', $this->server_caps)) { 378 | $this->setError('Authentication is not allowed at this stage'); 379 | // 'at this stage' means that auth may be allowed after the stage changes 380 | // e.g. after STARTTLS 381 | return false; 382 | } 383 | 384 | self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); 385 | self::edebug( 386 | 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), 387 | self::DEBUG_LOWLEVEL 388 | ); 389 | 390 | if (empty($authtype)) { 391 | foreach (array('LOGIN', 'CRAM-MD5', 'NTLM', 'PLAIN') as $method) { 392 | if (in_array($method, $this->server_caps['AUTH'])) { 393 | $authtype = $method; 394 | break; 395 | } 396 | } 397 | if (empty($authtype)) { 398 | $this->setError('No supported authentication methods found'); 399 | return false; 400 | } 401 | self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); 402 | } 403 | 404 | if (!in_array($authtype, $this->server_caps['AUTH'])) { 405 | $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); 406 | return false; 407 | } 408 | } elseif (empty($authtype)) { 409 | $authtype = 'LOGIN'; 410 | } 411 | switch ($authtype) { 412 | case 'PLAIN': 413 | // Start authentication 414 | if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { 415 | return false; 416 | } 417 | // Send encoded username and password 418 | if (!$this->sendCommand( 419 | 'User & Password', 420 | base64_encode("\0" . $username . "\0" . $password), 421 | 235 422 | ) 423 | ) { 424 | return false; 425 | } 426 | break; 427 | case 'LOGIN': 428 | // Start authentication 429 | if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { 430 | return false; 431 | } 432 | if (!$this->sendCommand("Username", base64_encode($username), 334)) { 433 | return false; 434 | } 435 | if (!$this->sendCommand("Password", base64_encode($password), 235)) { 436 | return false; 437 | } 438 | break; 439 | case 'NTLM': 440 | /* 441 | * ntlm_sasl_client.php 442 | * Bundled with Permission 443 | * 444 | * How to telnet in windows: 445 | * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx 446 | * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication 447 | */ 448 | require_once 'extras/ntlm_sasl_client.php'; 449 | $temp = new stdClass; 450 | $ntlm_client = new ntlm_sasl_client_class; 451 | //Check that functions are available 452 | if (!$ntlm_client->Initialize($temp)) { 453 | $this->setError($temp->error); 454 | $this->edebug( 455 | 'You need to enable some modules in your php.ini file: ' 456 | . $this->error['error'], 457 | self::DEBUG_CLIENT 458 | ); 459 | return false; 460 | } 461 | //msg1 462 | $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 463 | 464 | if (!$this->sendCommand( 465 | 'AUTH NTLM', 466 | 'AUTH NTLM ' . base64_encode($msg1), 467 | 334 468 | ) 469 | ) { 470 | return false; 471 | } 472 | //Though 0 based, there is a white space after the 3 digit number 473 | //msg2 474 | $challenge = substr($this->last_reply, 3); 475 | $challenge = base64_decode($challenge); 476 | $ntlm_res = $ntlm_client->NTLMResponse( 477 | substr($challenge, 24, 8), 478 | $password 479 | ); 480 | //msg3 481 | $msg3 = $ntlm_client->TypeMsg3( 482 | $ntlm_res, 483 | $username, 484 | $realm, 485 | $workstation 486 | ); 487 | // send encoded username 488 | return $this->sendCommand('Username', base64_encode($msg3), 235); 489 | case 'CRAM-MD5': 490 | // Start authentication 491 | if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { 492 | return false; 493 | } 494 | // Get the challenge 495 | $challenge = base64_decode(substr($this->last_reply, 4)); 496 | 497 | // Build the response 498 | $response = $username . ' ' . $this->hmac($challenge, $password); 499 | 500 | // send encoded credentials 501 | return $this->sendCommand('Username', base64_encode($response), 235); 502 | default: 503 | $this->setError("Authentication method \"$authtype\" is not supported"); 504 | return false; 505 | } 506 | return true; 507 | } 508 | 509 | /** 510 | * Calculate an MD5 HMAC hash. 511 | * Works like hash_hmac('md5', $data, $key) 512 | * in case that function is not available 513 | * @param string $data The data to hash 514 | * @param string $key The key to hash with 515 | * @access protected 516 | * @return string 517 | */ 518 | protected function hmac($data, $key) 519 | { 520 | if (function_exists('hash_hmac')) { 521 | return hash_hmac('md5', $data, $key); 522 | } 523 | 524 | // The following borrowed from 525 | // http://php.net/manual/en/function.mhash.php#27225 526 | 527 | // RFC 2104 HMAC implementation for php. 528 | // Creates an md5 HMAC. 529 | // Eliminates the need to install mhash to compute a HMAC 530 | // by Lance Rushing 531 | 532 | $bytelen = 64; // byte length for md5 533 | if (strlen($key) > $bytelen) { 534 | $key = pack('H*', md5($key)); 535 | } 536 | $key = str_pad($key, $bytelen, chr(0x00)); 537 | $ipad = str_pad('', $bytelen, chr(0x36)); 538 | $opad = str_pad('', $bytelen, chr(0x5c)); 539 | $k_ipad = $key ^ $ipad; 540 | $k_opad = $key ^ $opad; 541 | 542 | return md5($k_opad . pack('H*', md5($k_ipad . $data))); 543 | } 544 | 545 | /** 546 | * Check connection state. 547 | * @access public 548 | * @return boolean True if connected. 549 | */ 550 | public function connected() 551 | { 552 | if (is_resource($this->smtp_conn)) { 553 | $sock_status = stream_get_meta_data($this->smtp_conn); 554 | if ($sock_status['eof']) { 555 | // The socket is valid but we are not connected 556 | $this->edebug( 557 | 'SMTP NOTICE: EOF caught while checking if connected', 558 | self::DEBUG_CLIENT 559 | ); 560 | $this->close(); 561 | return false; 562 | } 563 | return true; // everything looks good 564 | } 565 | return false; 566 | } 567 | 568 | /** 569 | * Close the socket and clean up the state of the class. 570 | * Don't use this function without first trying to use QUIT. 571 | * @see quit() 572 | * @access public 573 | * @return void 574 | */ 575 | public function close() 576 | { 577 | $this->setError(''); 578 | $this->server_caps = null; 579 | $this->helo_rply = null; 580 | if (is_resource($this->smtp_conn)) { 581 | // close the connection and cleanup 582 | fclose($this->smtp_conn); 583 | $this->smtp_conn = null; //Makes for cleaner serialization 584 | $this->edebug('Connection: closed', self::DEBUG_CONNECTION); 585 | } 586 | } 587 | 588 | /** 589 | * Send an SMTP DATA command. 590 | * Issues a data command and sends the msg_data to the server, 591 | * finializing the mail transaction. $msg_data is the message 592 | * that is to be send with the headers. Each header needs to be 593 | * on a single line followed by a with the message headers 594 | * and the message body being separated by and additional . 595 | * Implements rfc 821: DATA 596 | * @param string $msg_data Message data to send 597 | * @access public 598 | * @return boolean 599 | */ 600 | public function data($msg_data) 601 | { 602 | //This will use the standard timelimit 603 | if (!$this->sendCommand('DATA', 'DATA', 354)) { 604 | return false; 605 | } 606 | 607 | /* The server is ready to accept data! 608 | * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) 609 | * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into 610 | * smaller lines to fit within the limit. 611 | * We will also look for lines that start with a '.' and prepend an additional '.'. 612 | * NOTE: this does not count towards line-length limit. 613 | */ 614 | 615 | // Normalize line breaks before exploding 616 | $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); 617 | 618 | /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field 619 | * of the first line (':' separated) does not contain a space then it _should_ be a header and we will 620 | * process all lines before a blank line as headers. 621 | */ 622 | 623 | $field = substr($lines[0], 0, strpos($lines[0], ':')); 624 | $in_headers = false; 625 | if (!empty($field) && strpos($field, ' ') === false) { 626 | $in_headers = true; 627 | } 628 | 629 | foreach ($lines as $line) { 630 | $lines_out = array(); 631 | if ($in_headers and $line == '') { 632 | $in_headers = false; 633 | } 634 | //Break this line up into several smaller lines if it's too long 635 | //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), 636 | while (isset($line[self::MAX_LINE_LENGTH])) { 637 | //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on 638 | //so as to avoid breaking in the middle of a word 639 | $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); 640 | //Deliberately matches both false and 0 641 | if (!$pos) { 642 | //No nice break found, add a hard break 643 | $pos = self::MAX_LINE_LENGTH - 1; 644 | $lines_out[] = substr($line, 0, $pos); 645 | $line = substr($line, $pos); 646 | } else { 647 | //Break at the found point 648 | $lines_out[] = substr($line, 0, $pos); 649 | //Move along by the amount we dealt with 650 | $line = substr($line, $pos + 1); 651 | } 652 | //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 653 | if ($in_headers) { 654 | $line = "\t" . $line; 655 | } 656 | } 657 | $lines_out[] = $line; 658 | 659 | //Send the lines to the server 660 | foreach ($lines_out as $line_out) { 661 | //RFC2821 section 4.5.2 662 | if (!empty($line_out) and $line_out[0] == '.') { 663 | $line_out = '.' . $line_out; 664 | } 665 | $this->client_send($line_out . self::CRLF); 666 | } 667 | } 668 | 669 | //Message data has been sent, complete the command 670 | //Increase timelimit for end of DATA command 671 | $savetimelimit = $this->Timelimit; 672 | $this->Timelimit = $this->Timelimit * 2; 673 | $result = $this->sendCommand('DATA END', '.', 250); 674 | //Restore timelimit 675 | $this->Timelimit = $savetimelimit; 676 | return $result; 677 | } 678 | 679 | /** 680 | * Send an SMTP HELO or EHLO command. 681 | * Used to identify the sending server to the receiving server. 682 | * This makes sure that client and server are in a known state. 683 | * Implements RFC 821: HELO 684 | * and RFC 2821 EHLO. 685 | * @param string $host The host name or IP to connect to 686 | * @access public 687 | * @return boolean 688 | */ 689 | public function hello($host = '') 690 | { 691 | //Try extended hello first (RFC 2821) 692 | return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); 693 | } 694 | 695 | /** 696 | * Send an SMTP HELO or EHLO command. 697 | * Low-level implementation used by hello() 698 | * @see hello() 699 | * @param string $hello The HELO string 700 | * @param string $host The hostname to say we are 701 | * @access protected 702 | * @return boolean 703 | */ 704 | protected function sendHello($hello, $host) 705 | { 706 | $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); 707 | $this->helo_rply = $this->last_reply; 708 | if ($noerror) { 709 | $this->parseHelloFields($hello); 710 | } else { 711 | $this->server_caps = null; 712 | } 713 | return $noerror; 714 | } 715 | 716 | /** 717 | * Parse a reply to HELO/EHLO command to discover server extensions. 718 | * In case of HELO, the only parameter that can be discovered is a server name. 719 | * @access protected 720 | * @param string $type - 'HELO' or 'EHLO' 721 | */ 722 | protected function parseHelloFields($type) 723 | { 724 | $this->server_caps = array(); 725 | $lines = explode("\n", $this->last_reply); 726 | foreach ($lines as $n => $s) { 727 | $s = trim(substr($s, 4)); 728 | if (!$s) { 729 | continue; 730 | } 731 | $fields = explode(' ', $s); 732 | if (!empty($fields)) { 733 | if (!$n) { 734 | $name = $type; 735 | $fields = $fields[0]; 736 | } else { 737 | $name = array_shift($fields); 738 | if ($name == 'SIZE') { 739 | $fields = ($fields) ? $fields[0] : 0; 740 | } 741 | } 742 | $this->server_caps[$name] = ($fields ? $fields : true); 743 | } 744 | } 745 | } 746 | 747 | /** 748 | * Send an SMTP MAIL command. 749 | * Starts a mail transaction from the email address specified in 750 | * $from. Returns true if successful or false otherwise. If True 751 | * the mail transaction is started and then one or more recipient 752 | * commands may be called followed by a data command. 753 | * Implements rfc 821: MAIL FROM: 754 | * @param string $from Source address of this message 755 | * @access public 756 | * @return boolean 757 | */ 758 | public function mail($from) 759 | { 760 | $useVerp = ($this->do_verp ? ' XVERP' : ''); 761 | return $this->sendCommand( 762 | 'MAIL FROM', 763 | 'MAIL FROM:<' . $from . '>' . $useVerp, 764 | 250 765 | ); 766 | } 767 | 768 | /** 769 | * Send an SMTP QUIT command. 770 | * Closes the socket if there is no error or the $close_on_error argument is true. 771 | * Implements from rfc 821: QUIT 772 | * @param boolean $close_on_error Should the connection close if an error occurs? 773 | * @access public 774 | * @return boolean 775 | */ 776 | public function quit($close_on_error = true) 777 | { 778 | $noerror = $this->sendCommand('QUIT', 'QUIT', 221); 779 | $err = $this->error; //Save any error 780 | if ($noerror or $close_on_error) { 781 | $this->close(); 782 | $this->error = $err; //Restore any error from the quit command 783 | } 784 | return $noerror; 785 | } 786 | 787 | /** 788 | * Send an SMTP RCPT command. 789 | * Sets the TO argument to $toaddr. 790 | * Returns true if the recipient was accepted false if it was rejected. 791 | * Implements from rfc 821: RCPT TO: 792 | * @param string $toaddr The address the message is being sent to 793 | * @access public 794 | * @return boolean 795 | */ 796 | public function recipient($toaddr) 797 | { 798 | return $this->sendCommand( 799 | 'RCPT TO', 800 | 'RCPT TO:<' . $toaddr . '>', 801 | array(250, 251) 802 | ); 803 | } 804 | 805 | /** 806 | * Send an SMTP RSET command. 807 | * Abort any transaction that is currently in progress. 808 | * Implements rfc 821: RSET 809 | * @access public 810 | * @return boolean True on success. 811 | */ 812 | public function reset() 813 | { 814 | return $this->sendCommand('RSET', 'RSET', 250); 815 | } 816 | 817 | /** 818 | * Send a command to an SMTP server and check its return code. 819 | * @param string $command The command name - not sent to the server 820 | * @param string $commandstring The actual command to send 821 | * @param integer|array $expect One or more expected integer success codes 822 | * @access protected 823 | * @return boolean True on success. 824 | */ 825 | protected function sendCommand($command, $commandstring, $expect) 826 | { 827 | if (!$this->connected()) { 828 | $this->setError("Called $command without being connected"); 829 | return false; 830 | } 831 | $this->client_send($commandstring . self::CRLF); 832 | 833 | $this->last_reply = $this->get_lines(); 834 | // Fetch SMTP code and possible error code explanation 835 | $matches = array(); 836 | if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { 837 | $code = $matches[1]; 838 | $code_ex = (count($matches) > 2 ? $matches[2] : null); 839 | // Cut off error code from each response line 840 | $detail = preg_replace( 841 | "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", 842 | '', 843 | $this->last_reply 844 | ); 845 | } else { 846 | // Fall back to simple parsing if regex fails 847 | $code = substr($this->last_reply, 0, 3); 848 | $code_ex = null; 849 | $detail = substr($this->last_reply, 4); 850 | } 851 | 852 | $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); 853 | 854 | if (!in_array($code, (array)$expect)) { 855 | $this->setError( 856 | "$command command failed", 857 | $detail, 858 | $code, 859 | $code_ex 860 | ); 861 | $this->edebug( 862 | 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, 863 | self::DEBUG_CLIENT 864 | ); 865 | return false; 866 | } 867 | 868 | $this->setError(''); 869 | return true; 870 | } 871 | 872 | /** 873 | * Send an SMTP SAML command. 874 | * Starts a mail transaction from the email address specified in $from. 875 | * Returns true if successful or false otherwise. If True 876 | * the mail transaction is started and then one or more recipient 877 | * commands may be called followed by a data command. This command 878 | * will send the message to the users terminal if they are logged 879 | * in and send them an email. 880 | * Implements rfc 821: SAML FROM: 881 | * @param string $from The address the message is from 882 | * @access public 883 | * @return boolean 884 | */ 885 | public function sendAndMail($from) 886 | { 887 | return $this->sendCommand('SAML', "SAML FROM:$from", 250); 888 | } 889 | 890 | /** 891 | * Send an SMTP VRFY command. 892 | * @param string $name The name to verify 893 | * @access public 894 | * @return boolean 895 | */ 896 | public function verify($name) 897 | { 898 | return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); 899 | } 900 | 901 | /** 902 | * Send an SMTP NOOP command. 903 | * Used to keep keep-alives alive, doesn't actually do anything 904 | * @access public 905 | * @return boolean 906 | */ 907 | public function noop() 908 | { 909 | return $this->sendCommand('NOOP', 'NOOP', 250); 910 | } 911 | 912 | /** 913 | * Send an SMTP TURN command. 914 | * This is an optional command for SMTP that this class does not support. 915 | * This method is here to make the RFC821 Definition complete for this class 916 | * and _may_ be implemented in future 917 | * Implements from rfc 821: TURN 918 | * @access public 919 | * @return boolean 920 | */ 921 | public function turn() 922 | { 923 | $this->setError('The SMTP TURN command is not implemented'); 924 | $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); 925 | return false; 926 | } 927 | 928 | /** 929 | * Send raw data to the server. 930 | * @param string $data The data to send 931 | * @access public 932 | * @return integer|boolean The number of bytes sent to the server or false on error 933 | */ 934 | public function client_send($data) 935 | { 936 | $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); 937 | return fwrite($this->smtp_conn, $data); 938 | } 939 | 940 | /** 941 | * Get the latest error. 942 | * @access public 943 | * @return array 944 | */ 945 | public function getError() 946 | { 947 | return $this->error; 948 | } 949 | 950 | /** 951 | * Get SMTP extensions available on the server 952 | * @access public 953 | * @return array|null 954 | */ 955 | public function getServerExtList() 956 | { 957 | return $this->server_caps; 958 | } 959 | 960 | /** 961 | * A multipurpose method 962 | * The method works in three ways, dependent on argument value and current state 963 | * 1. HELO/EHLO was not sent - returns null and set up $this->error 964 | * 2. HELO was sent 965 | * $name = 'HELO': returns server name 966 | * $name = 'EHLO': returns boolean false 967 | * $name = any string: returns null and set up $this->error 968 | * 3. EHLO was sent 969 | * $name = 'HELO'|'EHLO': returns server name 970 | * $name = any string: if extension $name exists, returns boolean True 971 | * or its options. Otherwise returns boolean False 972 | * In other words, one can use this method to detect 3 conditions: 973 | * - null returned: handshake was not or we don't know about ext (refer to $this->error) 974 | * - false returned: the requested feature exactly not exists 975 | * - positive value returned: the requested feature exists 976 | * @param string $name Name of SMTP extension or 'HELO'|'EHLO' 977 | * @return mixed 978 | */ 979 | public function getServerExt($name) 980 | { 981 | if (!$this->server_caps) { 982 | $this->setError('No HELO/EHLO was sent'); 983 | return null; 984 | } 985 | 986 | // the tight logic knot ;) 987 | if (!array_key_exists($name, $this->server_caps)) { 988 | if ($name == 'HELO') { 989 | return $this->server_caps['EHLO']; 990 | } 991 | if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { 992 | return false; 993 | } 994 | $this->setError('HELO handshake was used. Client knows nothing about server extensions'); 995 | return null; 996 | } 997 | 998 | return $this->server_caps[$name]; 999 | } 1000 | 1001 | /** 1002 | * Get the last reply from the server. 1003 | * @access public 1004 | * @return string 1005 | */ 1006 | public function getLastReply() 1007 | { 1008 | return $this->last_reply; 1009 | } 1010 | 1011 | /** 1012 | * Read the SMTP server's response. 1013 | * Either before eof or socket timeout occurs on the operation. 1014 | * With SMTP we can tell if we have more lines to read if the 1015 | * 4th character is '-' symbol. If it is a space then we don't 1016 | * need to read anything else. 1017 | * @access protected 1018 | * @return string 1019 | */ 1020 | protected function get_lines() 1021 | { 1022 | // If the connection is bad, give up straight away 1023 | if (!is_resource($this->smtp_conn)) { 1024 | return ''; 1025 | } 1026 | $data = ''; 1027 | $endtime = 0; 1028 | stream_set_timeout($this->smtp_conn, $this->Timeout); 1029 | if ($this->Timelimit > 0) { 1030 | $endtime = time() + $this->Timelimit; 1031 | } 1032 | while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { 1033 | $str = @fgets($this->smtp_conn, 515); 1034 | $this->edebug("SMTP -> get_lines(): \$data was \"$data\"", self::DEBUG_LOWLEVEL); 1035 | $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); 1036 | $data .= $str; 1037 | $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); 1038 | // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen 1039 | if ((isset($str[3]) and $str[3] == ' ')) { 1040 | break; 1041 | } 1042 | // Timed-out? Log and break 1043 | $info = stream_get_meta_data($this->smtp_conn); 1044 | if ($info['timed_out']) { 1045 | $this->edebug( 1046 | 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', 1047 | self::DEBUG_LOWLEVEL 1048 | ); 1049 | break; 1050 | } 1051 | // Now check if reads took too long 1052 | if ($endtime and time() > $endtime) { 1053 | $this->edebug( 1054 | 'SMTP -> get_lines(): timelimit reached ('. 1055 | $this->Timelimit . ' sec)', 1056 | self::DEBUG_LOWLEVEL 1057 | ); 1058 | break; 1059 | } 1060 | } 1061 | return $data; 1062 | } 1063 | 1064 | /** 1065 | * Enable or disable VERP address generation. 1066 | * @param boolean $enabled 1067 | */ 1068 | public function setVerp($enabled = false) 1069 | { 1070 | $this->do_verp = $enabled; 1071 | } 1072 | 1073 | /** 1074 | * Get VERP address generation mode. 1075 | * @return boolean 1076 | */ 1077 | public function getVerp() 1078 | { 1079 | return $this->do_verp; 1080 | } 1081 | 1082 | /** 1083 | * Set error messages and codes. 1084 | * @param string $message The error message 1085 | * @param string $detail Further detail on the error 1086 | * @param string $smtp_code An associated SMTP error code 1087 | * @param string $smtp_code_ex Extended SMTP code 1088 | */ 1089 | protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') 1090 | { 1091 | $this->error = array( 1092 | 'error' => $message, 1093 | 'detail' => $detail, 1094 | 'smtp_code' => $smtp_code, 1095 | 'smtp_code_ex' => $smtp_code_ex 1096 | ); 1097 | } 1098 | 1099 | /** 1100 | * Set debug output method. 1101 | * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. 1102 | */ 1103 | public function setDebugOutput($method = 'echo') 1104 | { 1105 | $this->Debugoutput = $method; 1106 | } 1107 | 1108 | /** 1109 | * Get debug output method. 1110 | * @return string 1111 | */ 1112 | public function getDebugOutput() 1113 | { 1114 | return $this->Debugoutput; 1115 | } 1116 | 1117 | /** 1118 | * Set debug output level. 1119 | * @param integer $level 1120 | */ 1121 | public function setDebugLevel($level = 0) 1122 | { 1123 | $this->do_debug = $level; 1124 | } 1125 | 1126 | /** 1127 | * Get debug output level. 1128 | * @return integer 1129 | */ 1130 | public function getDebugLevel() 1131 | { 1132 | return $this->do_debug; 1133 | } 1134 | 1135 | /** 1136 | * Set SMTP timeout. 1137 | * @param integer $timeout 1138 | */ 1139 | public function setTimeout($timeout = 0) 1140 | { 1141 | $this->Timeout = $timeout; 1142 | } 1143 | 1144 | /** 1145 | * Get SMTP timeout. 1146 | * @return integer 1147 | */ 1148 | public function getTimeout() 1149 | { 1150 | return $this->Timeout; 1151 | } 1152 | } --------------------------------------------------------------------------------