├── tests ├── bootstrap.php ├── phpunit.xml └── MxToolbox │ └── MxToolboxTest.php ├── src └── MxToolbox │ ├── Container │ ├── MxContainer.php │ └── MxToolboxContainer.php │ ├── Exceptions │ ├── MxToolboxLogicException.php │ └── MxToolboxRuntimeException.php │ ├── Python │ └── quickDig.py │ ├── autoload.php │ ├── NetworkTools │ ├── DigQueryParser.php │ ├── SmtpDiagnosticParser.php │ ├── QuickDig.php │ ├── NetworkTools.php │ └── SmtpServerChecks.php │ ├── FileSystem │ └── BlacklistsHostnameFile.php │ ├── DataGrid │ └── MxToolboxDataGrid.php │ └── MxToolbox.php ├── .codeclimate.yml ├── composer.json ├── examples ├── smtpDiagnostics.php ├── refreshDnsblFile.php ├── getAdditionalInformation.php ├── nagiosUsage.php ├── myOwnBlackList.php ├── basicUsage.php ├── userDefinedBlacklistFilesPath.php ├── basicMultiprocessingUsage.php └── complexExample.php ├── .travis.yml ├── LICENSE.md └── README.md /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | ./ 14 | 15 | 16 | 17 | 18 | ../* 19 | 20 | ../examples 21 | ../tests 22 | ../vendor 23 | ../src/MxToolbox/autoload.php 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mxtoolbox/mxtoolbox", 3 | "description": "IP blacklist tools", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "mxtoolbox", 8 | "dnsbl", 9 | "blacklist" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Lubomir Spacek", 14 | "homepage": "https://dns-tools.best-hosting.cz/" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.6.5", 19 | "mxtoolbox-blacklists/mxtoolbox-blacklists": ">=0.0.1" 20 | }, 21 | "require-dev": { 22 | "codeclimate/php-test-reporter": "dev-master" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "MxToolbox\\": "src/MxToolbox", 27 | "MxToolbox\\Container\\": "src/MxToolbox/Container", 28 | "MxToolbox\\DataGrid\\": "src/MxToolbox/DataGrid", 29 | "MxToolbox\\Exceptions\\": "src/MxToolbox/Exceptions", 30 | "MxToolbox\\FileSystem\\": "src/MxToolbox/FileSystem", 31 | "MxToolbox\\NetworkTools\\": "src/MxToolbox/NetworkTools" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /examples/smtpDiagnostics.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 18 | // set dns resolver - required 19 | //->setDnsResolver('8.8.8.8') 20 | //->setDnsResolver('8.8.4.4') 21 | ->setDnsResolver('127.0.0.1'); 22 | 23 | /** 24 | * Get SMTP server diagnostics responses 25 | */ 26 | var_dump($test->getSmtpDiagnosticsInfo( 27 | '64.12.91.197', 28 | 'google.com', 29 | 'mxtool@example.com', 30 | 'test@example.com' 31 | )); 32 | 33 | } catch (MxToolboxRuntimeException $e) { 34 | echo $e->getMessage().PHP_EOL; 35 | } catch (MxToolboxLogicException $e) { 36 | echo $e->getMessage().PHP_EOL; 37 | } 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - hhvm 7 | 8 | matrix: 9 | allow_failures: 10 | - php: hhvm 11 | 12 | addons: 13 | apt: 14 | packages: 15 | - dnsutils 16 | code_climate: 17 | repo_token: 78f6ef7ba154b03b8f3abbd9913bef999191427add26561220fd0fecd4b2d1e0 18 | 19 | script: 20 | - phpunit --configuration tests/phpunit.xml --coverage-text --coverage-clover build/logs/clover.xml 21 | 22 | before_script: 23 | - travis_retry composer self-update 24 | - travis_retry composer update 25 | 26 | after_script: 27 | - CODECLIMATE_REPO_TOKEN="78f6ef7ba154b03b8f3abbd9913bef999191427add26561220fd0fecd4b2d1e0" ./vendor/bin/test-reporter --stdout > codeclimate.json 28 | - "curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports" 29 | 30 | before_install: 31 | - pip install --user codecov 32 | 33 | after_success: 34 | - bash <(curl -s https://codecov.io/bash) 35 | 36 | branches: 37 | only: 38 | - master 39 | - stable 40 | 41 | -------------------------------------------------------------------------------- /examples/refreshDnsblFile.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1'); 25 | 26 | /* 27 | * Update the blacklistAlive.txt file - ! time-consuming process ! 28 | */ 29 | $test->updateAliveBlacklistFile(); 30 | 31 | /* 32 | * Get new test array prepared for check (without any test results) 33 | */ 34 | var_dump($test->getBlacklistsArray()); 35 | 36 | } catch (MxToolboxRuntimeException $e) { 37 | echo $e->getMessage(); 38 | } catch (MxToolboxLogicException $e) { 39 | echo $e->getMessage(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/getAdditionalInformation.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1'); 25 | 26 | /* Get additional information for IP address 27 | * Return array structure: 28 | * ['ipAddress'] 29 | * ['domainName'] 30 | * ['ptrRecord'] 31 | * ['mxRecords'][array] 32 | */ 33 | var_dump($test->getDomainInformation('8.8.8.8')); 34 | 35 | } catch (MxToolboxRuntimeException $e) { 36 | echo $e->getMessage(); 37 | } catch (MxToolboxLogicException $e) { 38 | echo $e->getMessage(); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MX Toolbox is released under the MIT license. 2 | 3 | Copyright (c) 2016 Lubomir Spacek 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the "Software"), 7 | > to deal in the Software without restriction, including without limitation the 8 | > rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | > sell copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | 12 | > The above copyright notice and this permission notice shall be included 13 | > in all copies or substantial portions of the Software. 14 | 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | > THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | > OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 20 | > ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | > OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/MxToolbox/Python/quickDig.py: -------------------------------------------------------------------------------- 1 | import json, sys, subprocess, multiprocessing 2 | 3 | 4 | class DataObject: 5 | def __init__(self, params): 6 | self.domain, self.ip, self.digPath, self.resolver = params 7 | 8 | 9 | def run_process(obj_instance): 10 | domain, ip, digPath, resolver = obj_instance.domain, obj_instance.ip, obj_instance.digPath, obj_instance.resolver 11 | processed = [] 12 | processed.append(subprocess.check_output([digPath + ' @' + resolver + ' ' + ip + '.' + domain + ' TXT'], shell=True)) 13 | return processed 14 | 15 | 16 | if __name__ == '__main__': 17 | """ sys.argv[1] = test ip address (must be reversed) 18 | sys.argv[2] = dig path (/usr/bin/dig) 19 | sys.argv[3] = resolver ip address 20 | sys.argv[4] = dnsbl host names json array 21 | Usage example: python ./quickDig.py 2.0.0.127 /usr/bin/dig 127.0.0.1 [] 22 | """ 23 | try: 24 | blacklist = json.loads(sys.argv[4]) 25 | objList = [] 26 | for domain in blacklist: 27 | objList.append(DataObject((domain, sys.argv[1], sys.argv[2], sys.argv[3]))) 28 | pool = multiprocessing.Pool() 29 | results = pool.map(run_process, objList) 30 | print(json.dumps(results)) 31 | except Exception as e: 32 | print ('error') 33 | -------------------------------------------------------------------------------- /src/MxToolbox/autoload.php: -------------------------------------------------------------------------------- 1 | 'MxToolbox.php', 9 | 'MxToolbox\IMxToolbox' => 'IMxToolbox.php', 10 | 'MxToolbox\Container\MxToolboxContainer' => 'Container/MxToolboxContainer.php', 11 | 'MxToolbox\Container\MxContainer' => 'Container/MxContainer.php', 12 | 'MxToolbox\Exceptions\MxToolboxLogicException' => 'Exceptions/MxToolboxLogicException.php', 13 | 'MxToolbox\Exceptions\MxToolboxRuntimeException' => 'Exceptions/MxToolboxRuntimeException.php', 14 | 'MxToolbox\FileSystem\BlacklistsHostnameFile' => 'FileSystem/BlacklistsHostnameFile.php', 15 | 'MxToolbox\DataGrid\MxToolboxDataGrid' => 'DataGrid/MxToolboxDataGrid.php', 16 | 'MxToolbox\NetworkTools\NetworkTools' => 'NetworkTools/NetworkTools.php', 17 | 'MxToolbox\NetworkTools\QuickDig' => 'NetworkTools/QuickDig.php', 18 | 'MxToolbox\NetworkTools\SmtpServerChecks' => 'NetworkTools/SmtpServerChecks.php', 19 | 'MxToolbox\NetworkTools\SmtpDiagnosticParser' => 'NetworkTools/SmtpDiagnosticParser.php', 20 | 'MxToolbox\NetworkTools\DigQueryParser' => 'NetworkTools/DigQueryParser.php' 21 | ]; 22 | 23 | if (isset($map[$class])) 24 | require __DIR__ . DIRECTORY_SEPARATOR . $map[$class]; 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /examples/nagiosUsage.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | ->setDnsResolver('127.0.0.1') 23 | // load default blacklists for dnsbl check - required in this example 24 | ->setBlacklists(); 25 | 26 | /** 27 | * Check any IP address on all DNSBL 28 | */ 29 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 30 | 31 | /** 32 | * Check any IP address on all DNSBL 33 | * Multiprocessing - much faster but experimental in v0.1.0 34 | */ 35 | //$test->checkIpAddressOnDnsbl('8.8.8.8', true); 36 | 37 | 38 | /** 39 | * Nagios return POSIX code 40 | * https://nagios-plugins.org/doc/guidelines.html#AEN78 41 | * Positive check - return WARNING 42 | * All OK - return OK 43 | * Exception - return UNKNOWN 44 | */ 45 | foreach ($test->getBlacklistsArray() as $list) { 46 | if($list['blPositive']) { 47 | print('WARNING'); 48 | exit(1); 49 | } 50 | } 51 | print('OK'); 52 | exit(0); 53 | 54 | } catch (MxToolboxRuntimeException $e) { 55 | print('UNKNOWN'); 56 | exit(3); 57 | } catch (MxToolboxLogicException $e) { 58 | print('UNKNOWN'); 59 | exit(3); 60 | } catch (Exception $e) { 61 | print('UNKNOWN'); 62 | exit(3); 63 | } 64 | -------------------------------------------------------------------------------- /examples/myOwnBlackList.php: -------------------------------------------------------------------------------- 1 | 'zen.spamhaus.org', 13 | 1 => 'xbl.spamhaus.org' 14 | ); 15 | 16 | try { 17 | 18 | $test = new MxToolbox(); 19 | 20 | /** 21 | * Configure MxToolbox 22 | */ 23 | $test 24 | // path to the dig tool - required 25 | ->setDig('/usr/bin/dig') 26 | // set dns resolver - required 27 | //->setDnsResolver('8.8.8.8') 28 | //->setDnsResolver('8.8.4.4') 29 | ->setDnsResolver('127.0.0.1') 30 | // load user defined blacklists for dnsbl check 31 | ->setBlacklists($myBlacklist); 32 | 33 | /* 34 | * Check IP address on all DNSBL 35 | */ 36 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 37 | 38 | /* 39 | * Get the same array but with a check results 40 | * 41 | * Return structure: 42 | * []['blHostName'] = dnsbl hostname 43 | * []['blPositive'] = true if IP address have the positive check 44 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 45 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 46 | * []['blQueryTime'] = false or response time of a last dig query 47 | */ 48 | var_dump($test->getBlacklistsArray()); 49 | 50 | /* 51 | * Cleaning old results - REQUIRED only in loop before next test 52 | * TRUE = check responses for all DNSBL again (default value) 53 | * FALSE = only cleaning old results ([blResponse] => true) 54 | */ 55 | $test->cleanBlacklistArray(false); 56 | 57 | } catch (MxToolboxRuntimeException $e) { 58 | echo $e->getMessage(); 59 | } catch (MxToolboxLogicException $e) { 60 | echo $e->getMessage(); 61 | } 62 | -------------------------------------------------------------------------------- /examples/basicUsage.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1') 25 | // load default blacklists for dnsbl check - optional 26 | ->setBlacklists(); 27 | 28 | /** 29 | * Get test array prepared for check if you need (without any test results) 30 | */ 31 | //var_dump($this->getBlacklistsArray()); 32 | 33 | /** 34 | * Check IP address on all DNSBL 35 | */ 36 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 37 | 38 | /** 39 | * Get the same array but with a check results 40 | * 41 | * Return structure: 42 | * []['blHostName'] = dnsbl hostname 43 | * []['blPositive'] = true if IP address have the positive check 44 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 45 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 46 | * []['blQueryTime'] = false or response time of a last dig query 47 | */ 48 | 49 | var_dump($test->getBlacklistsArray()); 50 | /** 51 | * Cleaning old results - REQUIRED only in loop before next test 52 | * TRUE = check responses for all DNSBL again (default value) 53 | * FALSE = only cleaning old results ([blResponse] => true) 54 | */ 55 | $test->cleanBlacklistArray(false); 56 | 57 | } catch (MxToolboxRuntimeException $e) { 58 | echo $e->getMessage(); 59 | } catch (MxToolboxLogicException $e) { 60 | echo $e->getMessage(); 61 | } 62 | -------------------------------------------------------------------------------- /examples/userDefinedBlacklistFilesPath.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1') 25 | // set user path to the blacklist files - optional 26 | ->setBlacklistFilePath(dirname(__FILE__) . '/../vendor/mxtoolbox-blacklists/mxtoolbox-blacklists/') 27 | // load default blacklists for dnsbl check - optional 28 | ->setBlacklists(); 29 | 30 | /* 31 | * Check IP address on all DNSBL 32 | */ 33 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 34 | 35 | /* 36 | * Get array with a check results 37 | * 38 | * Return structure: 39 | * []['blHostName'] = dnsbl hostname 40 | * []['blPositive'] = true if IP address have the positive check 41 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 42 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 43 | * []['blQueryTime'] = false or response time of a last dig query 44 | */ 45 | var_dump($test->getBlacklistsArray()); 46 | 47 | /* 48 | * Cleaning old results - REQUIRED only in loop before next test 49 | * TRUE = check responses for all DNSBL again (default value) 50 | * FALSE = only cleaning old results ([blResponse] => true) 51 | */ 52 | $test->cleanBlacklistArray(false); 53 | 54 | } catch (MxToolboxRuntimeException $e) { 55 | echo $e->getMessage(); 56 | } catch (MxToolboxLogicException $e) { 57 | echo $e->getMessage(); 58 | } 59 | -------------------------------------------------------------------------------- /examples/basicMultiprocessingUsage.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1') 25 | // load default blacklists for dnsbl check - optional 26 | ->setBlacklists(); 27 | 28 | /** 29 | * Get test array prepared for check if you need (without any test results) 30 | */ 31 | //var_dump($this->getBlacklistsArray()); 32 | 33 | /** 34 | * Check IP address on all DNSBL 35 | * TRUE for using multiprocessing dig command (Python >=2.7 required!) 36 | * FALSE (default) 37 | */ 38 | $test->checkIpAddressOnDnsbl('8.8.8.8', true); 39 | 40 | /** 41 | * Get the same array but with a check results 42 | * 43 | * Return structure: 44 | * []['blHostName'] = dnsbl hostname 45 | * []['blPositive'] = true if IP address have the positive check 46 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 47 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 48 | * []['blQueryTime'] = false or response time of a last dig query 49 | */ 50 | 51 | var_dump($test->getBlacklistsArray()); 52 | /** 53 | * Cleaning old results - REQUIRED only in loop before next test 54 | * TRUE = check responses for all DNSBL again (default value) 55 | * FALSE = only cleaning old results ([blResponse] => true) 56 | */ 57 | $test->cleanBlacklistArray(false); 58 | 59 | } catch (MxToolboxRuntimeException $e) { 60 | echo $e->getMessage(); 61 | } catch (MxToolboxLogicException $e) { 62 | echo $e->getMessage(); 63 | } 64 | -------------------------------------------------------------------------------- /src/MxToolbox/NetworkTools/DigQueryParser.php: -------------------------------------------------------------------------------- 1 | setDig('/usr/bin/dig') 21 | // set dns resolver - required 22 | //->setDnsResolver('8.8.8.8') 23 | //->setDnsResolver('8.8.4.4') 24 | ->setDnsResolver('127.0.0.1') 25 | // set user path to the blacklist files - optional 26 | //->setBlacklistFilePath(); 27 | // load default blacklists for dnsbl check - optional 28 | ->setBlacklists(); 29 | /* 30 | * Get test array prepared for check (without any test results) 31 | */ 32 | //var_dump($this->getBlacklistsArray()); 33 | 34 | /* 35 | * Check IP address on all DNSBL 36 | */ 37 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 38 | 39 | /* 40 | * Get the same array but with a check results 41 | * 42 | * Return structure: 43 | * []['blHostName'] = dnsbl hostname 44 | * []['blPositive'] = true if IP address have the positive check 45 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 46 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 47 | * []['blQueryTime'] = false or response time of a last dig query 48 | */ 49 | 50 | var_dump($test->getBlacklistsArray()); 51 | 52 | /* 53 | * Cleaning old results - REQUIRED only in loop before next test 54 | * TRUE = check responses for all DNSBL again (default value) 55 | * FALSE = only cleaning old results ([blResponse] => true) 56 | */ 57 | $test->cleanBlacklistArray(false); 58 | 59 | // Get SMTP server diagnostics responses 60 | // '64.12.91.197' is any public SMTP server 61 | var_dump($this->getSmtpDiagnosticsInfo( 62 | '64.12.91.197', 63 | 'google.com', 64 | 'mxtool@example.com', 65 | 'test@example.com' 66 | )); 67 | 68 | /* Get additional information for IP address 69 | * Array structure: 70 | * ['ipAddress'] 71 | * ['domainName'] 72 | * ['ptrRecord'] 73 | * ['mxRecords'][array] 74 | */ 75 | var_dump($test->getDomainInformation('8.8.8.8')); 76 | 77 | // get array with dns resolvers 78 | var_dump($test->getDnsResolvers()); 79 | 80 | // get DIG path 81 | var_dump($test->getDigPath()); 82 | 83 | } catch (MxToolboxRuntimeException $e) { 84 | echo $e->getMessage(); 85 | } catch (MxToolboxLogicException $e) { 86 | echo $e->getMessage(); 87 | } 88 | -------------------------------------------------------------------------------- /src/MxToolbox/Container/MxToolboxContainer.php: -------------------------------------------------------------------------------- 1 | container['quickDig']) || !$this->container['quickDig'] instanceof QuickDig) 38 | $this->container['quickDig'] = new QuickDig($this->createServiceNetworkTool()); 39 | return $this->container['quickDig']; 40 | } 41 | 42 | /** 43 | * Create service NetworkTool 44 | * @return NetworkTools 45 | */ 46 | protected function createServiceNetworkTool() 47 | { 48 | if (!isset($this->container['netTool']) || !$this->container['netTool'] instanceof NetworkTools) 49 | $this->container['netTool'] = new NetworkTools(); 50 | return $this->container['netTool']; 51 | } 52 | 53 | /** 54 | * Create service BlacklistsHostnameFile 55 | * @return BlacklistsHostnameFile 56 | */ 57 | protected function createServiceBlacklistsHostnameFile() 58 | { 59 | if (!isset($this->container['fileSys']) || !$this->container['fileSys'] instanceof BlacklistsHostnameFile) 60 | $this->container['fileSys'] = new BlacklistsHostnameFile(); 61 | return $this->container['fileSys']; 62 | } 63 | 64 | /** 65 | * Create service MxToolboxDataGrid 66 | * @return MxToolboxDataGrid 67 | */ 68 | protected function createServiceMxToolboxDataGrid() 69 | { 70 | if (!isset($this->container['dataGrid']) || !$this->container['dataGrid'] instanceof MxToolboxDataGrid) 71 | $this->container['dataGrid'] = new MxToolboxDataGrid( 72 | $this->createServiceBlacklistsHostnameFile(), 73 | $this->createServiceNetworkTool() 74 | ); 75 | return $this->container['dataGrid']; 76 | } 77 | 78 | /** 79 | * Create service MxToolbox\NetworkTools\SmtpServerChecks 80 | * @param string $addr 81 | * @param string $myHostName 82 | * @param string $mailFrom 83 | * @param string $mailRcptTo 84 | * @return SmtpServerChecks 85 | */ 86 | protected function createServiceSmtpServerChecks($addr, $myHostName, $mailFrom, $mailRcptTo) 87 | { 88 | if (!isset($this->container['smtpDiagnostics']) || !$this->container['smtpDiagnostics'] instanceof SmtpServerChecks) 89 | $this->container['smtpDiagnostics'] = new SmtpServerChecks( 90 | $this->createServiceNetworkTool(), 91 | $addr, 92 | $myHostName, 93 | $mailFrom, 94 | $mailRcptTo 95 | ); 96 | return $this->container['smtpDiagnostics']; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MXToolbox 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/mxtoolbox/mxtoolbox/v/stable)](https://github.com/heximcz/mxtoolbox/releases) 4 | [![Build Status](https://travis-ci.org/heximcz/mxtoolbox.svg?branch=master)](https://travis-ci.org/heximcz/mxtoolbox) 5 | [![Latest Unstable Version](https://poser.pugx.org/mxtoolbox/mxtoolbox/v/unstable)](https://github.com/heximcz/mxtoolbox) 6 | [![License](https://poser.pugx.org/mxtoolbox/mxtoolbox/license)](https://github.com/heximcz/mxtoolbox/blob/master/LICENSE.md) 7 | [![codecov.io](https://codecov.io/github/heximcz/mxtoolbox/coverage.svg?branch=master)](https://codecov.io/github/heximcz/mxtoolbox?branch=master) 8 | [![Code Climate](https://codeclimate.com/github/heximcz/mxtoolbox/badges/gpa.svg)](https://codeclimate.com/github/heximcz/mxtoolbox) 9 | 10 | ## Homepage 11 | [DNS TOOLS](https://dns-tools.best-hosting.cz/) 12 | 13 | ## Prerequisites 14 | 15 | - PHP > 5.6.x 16 | - Installed dnsutils (dig) 17 | 18 | ## Installation / Usage 19 | 20 | - 1. Via composer 21 | 22 | ``` 23 | composer require mxtoolbox/mxtoolbox 24 | ``` 25 | 26 | - 2. Create a composer.json defining your dependencies. 27 | 28 | ```json 29 | { 30 | "require": { 31 | "mxtoolbox/mxtoolbox": ">=0.0.1" 32 | } 33 | } 34 | ``` 35 | 36 | - 3. Example usage: 37 | 38 | ``` php 39 | setDig('/usr/bin/dig') 56 | // set dns resolver - required 57 | //->setDnsResolver('8.8.8.8') 58 | //->setDnsResolver('8.8.4.4') 59 | ->setDnsResolver('127.0.0.1') 60 | // load default blacklists for dnsbl check - optional 61 | ->setBlacklists(); 62 | 63 | /** 64 | * Get test array prepared for check if you need (without any test results) 65 | */ 66 | //var_dump($this->getBlacklistsArray()); 67 | 68 | /** 69 | * Check IP address on all DNSBL 70 | */ 71 | $test->checkIpAddressOnDnsbl('8.8.8.8'); 72 | 73 | /** 74 | * Get the same array but with a check results 75 | * 76 | * Return structure: 77 | * []['blHostName'] = dnsbl hostname 78 | * []['blPositive'] = true if IP address have the positive check 79 | * []['blPositiveResult'] = array() array of a URL addresses if IP address have the positive check 80 | * []['blResponse'] = true if DNSBL host name is alive or DNSBL responded during the test 81 | * []['blQueryTime'] = false or response time of a last dig query 82 | */ 83 | 84 | var_dump($test->getBlacklistsArray()); 85 | /** 86 | * Cleaning old results - REQUIRED only in loop before next test 87 | * TRUE = check responses for all DNSBL again (default value) 88 | * FALSE = only cleaning old results ([blResponse] => true) 89 | */ 90 | $test->cleanBlacklistArray(false); 91 | 92 | } catch (MxToolboxRuntimeException $e) { 93 | echo $e->getMessage(); 94 | } catch (MxToolboxLogicException $e) { 95 | echo $e->getMessage(); 96 | } 97 | ``` 98 | 99 | [More examples](https://github.com/heximcz/mxtoolbox/tree/master/examples) 100 | 101 | ## License 102 | 103 | [MIT](https://github.com/heximcz/mxtoolbox/blob/master/LICENSE.md) 104 | -------------------------------------------------------------------------------- /src/MxToolbox/NetworkTools/SmtpDiagnosticParser.php: -------------------------------------------------------------------------------- 1 | Service ready 24 | 221 Service closing transmission channel 25 | 250 Requested mail action okay, completed 26 | 251 User not local; will forward to 27 | 252 Cannot VRFY user, but will accept message and attempt delivery 28 | 354 Start mail input; end with . 29 | 421 Service not available, closing transmission channel 30 | 450 Requested mail action not taken: mailbox unavailable 31 | 451 Requested action aborted: local error in processing 32 | 452 Requested action not taken: insufficient system storage 33 | 500 Syntax error, command unrecognised 34 | 501 Syntax error in parameters or arguments 35 | 502 Command not implemented 36 | 503 Bad sequence of commands 37 | 504 Command parameter not implemented 38 | 521 does not accept mail (see rfc1846) 39 | 530 Access denied (???a Sendmailism) 40 | 550 Requested action not taken: mailbox unavailable 41 | 551 User not local; please try 42 | 552 Requested mail action aborted: exceeded storage allocation 43 | 553 Requested action not taken: mailbox name not allowed 44 | 554 Transaction failed 45 | */ 46 | 47 | /** 48 | * Test if START TLS is present in EHLO array 49 | * @param array $smtpOutput Output array from EHLO SMTP command 50 | * @return bool 51 | */ 52 | public function isTls($smtpOutput) 53 | { 54 | foreach ($smtpOutput as $value) { 55 | if (preg_match('/^250\-STARTTLS/', $value)) 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | /** 62 | * Check if PTR record corresponds with SMTP EHLO answer 63 | * @param array $smtpOutput 64 | * @param string $ptrRecord 65 | * @return bool 66 | */ 67 | public function isValidHostname($smtpOutput, $ptrRecord) 68 | { 69 | foreach ($smtpOutput as $value) { 70 | if (preg_match('/^250\-' . preg_quote(strtolower($ptrRecord), '.-') . '/', $value)) 71 | return true; 72 | } 73 | return false; 74 | 75 | } 76 | 77 | /** 78 | * Find ip address from ptr record, TRUE = all OK and not a reverse DNS mismatch 79 | * @param string $addr IP address 80 | * @param array $aRecords array from dns_get_record($info['ptrRecord'], DNS_A) 81 | * @return bool 82 | */ 83 | public function isReverseDnsMismatch($addr, $aRecords) 84 | { 85 | foreach ($aRecords as $idx => $value) { 86 | if ($value['ip'] == $addr) 87 | return true; 88 | } 89 | return false; 90 | } 91 | 92 | /** 93 | * Check SMTP banner response and PTR match 94 | * @param string $smtpBanner 95 | * @param string $ptrRecord 96 | * @return bool 97 | */ 98 | public function isReverseDnsInBanner($smtpBanner, $ptrRecord) 99 | { 100 | if (preg_match('/^220/', $smtpBanner) 101 | && preg_match('/' . preg_quote(strtolower($ptrRecord), '.-') . '/', $smtpBanner) 102 | ) 103 | return true; 104 | return false; 105 | } 106 | 107 | //string(55) "554 5.7.1 : Relay access denied" 108 | 109 | /** 110 | * Is relay access denied, code 250 is open relay! 111 | * @param string $rcptTo 112 | * @return bool 113 | */ 114 | public function isOpenRelay($rcptTo) 115 | { 116 | if (!preg_match('/^250/', $rcptTo)) 117 | return true; 118 | return false; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/MxToolbox/NetworkTools/QuickDig.php: -------------------------------------------------------------------------------- 1 | netTool = $netTool; 40 | $this->digParser = new DigQueryParser(); 41 | } 42 | 43 | /** 44 | * Start dig multiprocessing - fast process how to run multiple dig tasks in the same time 45 | * @param string $addr 46 | * @param array $testResult 47 | * @return $this 48 | */ 49 | public function getJsonFromDigMultiprocess($addr, &$testResult) 50 | { 51 | $this->ipAddress = $addr; 52 | $this->testResult = &$testResult; 53 | $this->netTool->ipValidator($this->ipAddress); 54 | $this->ipAddress = $this->netTool->getIpAddressFromDomainName($this->ipAddress); 55 | if (!count($this->testResult) > 0) 56 | throw new MxToolboxRuntimeException(sprintf('Array is empty for dig checks in: %s\%s.', get_class(), __FUNCTION__)); 57 | // prepare domain names only for python script 58 | $dnsblDomainNames = array(); 59 | foreach ($this->testResult as $item) { 60 | if ($item['blResponse']) 61 | $dnsblDomainNames[] = $item['blHostName']; 62 | } 63 | // call python script 64 | $this->digOutput = shell_exec( 65 | 'python ' . 66 | dirname(__FILE__) . 67 | DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'Python' . DIRECTORY_SEPARATOR . 'quickDig.py ' . 68 | $this->netTool->reverseIP($this->ipAddress) . ' ' . 69 | $this->netTool->getDigPath() . ' ' . 70 | $this->netTool->getRandomDNSResolverIP() . ' ' . 71 | escapeshellarg(json_encode($dnsblDomainNames)) 72 | ); 73 | // check errors 74 | if($this->digOutput == 'error') 75 | throw new MxToolboxRuntimeException('Python multiprocessing script exception!'); 76 | // parse json to array 77 | if(!empty($this->digOutput) && $this->isJson($this->digOutput)) { 78 | $this->digOutput = json_decode($this->digOutput); 79 | return $this; 80 | } 81 | throw new MxToolboxRuntimeException('Python digOutput is empty or does not json format.'); 82 | } 83 | 84 | /** 85 | * Parse data array returned from multiprocessing 86 | * @return $this 87 | */ 88 | public function parseDataFromMultiprocessing() 89 | { 90 | if(is_array($this->digOutput) && count($this->digOutput) > 0) { 91 | 92 | foreach ($this->testResult as &$blackList) { 93 | if ($digOutput = $this->searchDigResult($blackList['blHostName'])) { 94 | if ($this->digParser->isNoError($digOutput)) { 95 | $blackList['blPositive'] = true; 96 | $blackList['blPositiveResult'] = $this->digParser->getPositiveUrlAddresses($digOutput); 97 | } 98 | $blackList['blQueryTime'] = $this->digParser->getQueryTime($digOutput); 99 | } 100 | } 101 | //var_dump($this->testResult); 102 | return $this; 103 | } 104 | throw new MxToolboxRuntimeException('DigOutput is empty or does not array. Maybe call getJsonFromDigMultiprocess() first.'); 105 | } 106 | 107 | /** 108 | * Search dig output for a specific dnsbl domain 109 | * @param string $domainName 110 | * @return false|string 111 | */ 112 | private function searchDigResult($domainName) { 113 | foreach ($this->digOutput as $item) { 114 | if (($firstLine = strtok($item[0], "\n")) !== false) { 115 | if ($this->digParser->isDomainNameInString($domainName, $firstLine)) 116 | return $item[0]; 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | /** 123 | * is json 124 | * @param string $string 125 | * @return bool 126 | */ 127 | private function isJson($string) { 128 | json_decode($string); 129 | return (json_last_error() == JSON_ERROR_NONE); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/MxToolbox/FileSystem/BlacklistsHostnameFile.php: -------------------------------------------------------------------------------- 1 | blacklistHostNames) && count($this->blacklistHostNames) > 0) 37 | return $this->blacklistHostNames; 38 | throw new MxToolboxLogicException('Array is empty, load blacklist first.'); 39 | } 40 | 41 | /** 42 | * Load blacklists host names from a file 43 | * 44 | * @param string $fileName 45 | * @throws MxToolboxRuntimeException; 46 | * @throws MxToolboxLogicException; 47 | * @return $this 48 | */ 49 | public function loadBlacklistsFromFile($fileName) 50 | { 51 | // user not defined any path 52 | if (empty($this->blacklistPath)) 53 | $this->setBlacklistFilePath(); 54 | 55 | $blFile = $this->blacklistPath . $fileName; 56 | if (!is_readable($blFile)) 57 | throw new MxToolboxRuntimeException("Blacklists file does not exist in: " . $blFile, 400); 58 | 59 | if (!($this->blacklistHostNames = file($blFile, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES)) === false) { 60 | if (!count($this->blacklistHostNames) > 0) { 61 | throw new MxToolboxLogicException(sprintf('Blacklist file' . $blFile . ' is empty in %s\%s()', 62 | get_class(), __FUNCTION__)); 63 | } 64 | return $this; 65 | } 66 | throw new MxToolboxRuntimeException(sprintf('Cannot get contents from: ' . $blFile . ' in %s\%s()', 67 | get_class(), __FUNCTION__), 500); 68 | } 69 | 70 | /** 71 | * Build new file with alive DNSBLs host names 72 | * 73 | * @param array $aliveBlacklists 74 | * @return $this 75 | * @throws MxToolboxRuntimeException 76 | */ 77 | public function makeAliveBlacklistFile(&$aliveBlacklists) 78 | { 79 | if (!array_key_exists('blHostName', $aliveBlacklists[0])) 80 | throw new MxToolboxRuntimeException("Cannot found index ['blHostName'] in array. Build test array first."); 81 | 82 | $blAliveFileTmp = $this->blacklistPath . 'blacklistsAlive.tmp'; 83 | $blAliveFileOrg = $this->blacklistPath . 'blacklistsAlive.txt'; 84 | 85 | // create temp file 86 | if (!@$file = fopen($blAliveFileTmp, 'w')) 87 | throw new MxToolboxRuntimeException ('Cannot create new file: ' . $blAliveFileTmp); 88 | 89 | foreach ($aliveBlacklists as $blackList) { 90 | if ($blackList['blResponse']) { 91 | fwrite($file, $blackList['blHostName'] . PHP_EOL); 92 | } 93 | } 94 | fclose($file); 95 | 96 | // check file size 97 | if (!filesize($blAliveFileTmp) > 0) { 98 | @unlink($blAliveFileTmp); 99 | throw new MxToolboxRuntimeException ('Blacklist temp file is empty: ' . $blAliveFileTmp); 100 | } 101 | // create new blacklist file from temp 102 | if (!rename($blAliveFileTmp, $blAliveFileOrg)) 103 | throw new MxToolboxRuntimeException('Cannot create Alive Blacklist file. Rename the file failed.'); 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Delete alive blacklists file if exist 110 | * @return $this 111 | */ 112 | public function deleteAliveBlacklist() 113 | { 114 | if (empty($this->blacklistPath)) 115 | $this->setBlacklistFilePath(); 116 | $blAliveFile = $this->blacklistPath . 'blacklistsAlive.txt'; 117 | if (is_readable($blAliveFile)) 118 | @unlink($blAliveFile); 119 | return $this; 120 | } 121 | 122 | /** 123 | * Set blacklist file path 124 | * @param string|boolean $path - default FALSE for auto configuration 125 | * @return $this 126 | */ 127 | public function setBlacklistFilePath($path=false) 128 | { 129 | // user configuration path 130 | if (is_string($path)) { 131 | $this->blacklistPath = $path; 132 | return $this; 133 | } 134 | 135 | // standard composer installation 136 | $this->blacklistPath = dirname(__FILE__) . 137 | DIRECTORY_SEPARATOR . '..' . 138 | DIRECTORY_SEPARATOR . '..' . 139 | DIRECTORY_SEPARATOR . '..' . 140 | DIRECTORY_SEPARATOR . '..' . 141 | DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 142 | 'mxtoolbox-blacklists' . DIRECTORY_SEPARATOR . 143 | 'mxtoolbox-blacklists' . DIRECTORY_SEPARATOR; 144 | 145 | if (!file_exists($this->blacklistPath . 'blacklists.txt')) { 146 | // install blacklist files directly to mxtoolbox (travis,...) 147 | $this->blacklistPath = dirname(__FILE__) . 148 | DIRECTORY_SEPARATOR . '..' . 149 | DIRECTORY_SEPARATOR . '..' . 150 | DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 151 | 'vendor' . DIRECTORY_SEPARATOR . 152 | 'mxtoolbox-blacklists' . DIRECTORY_SEPARATOR . 153 | 'mxtoolbox-blacklists' . DIRECTORY_SEPARATOR; 154 | if (!file_exists($this->blacklistPath . 'blacklists.txt')) { 155 | throw new MxToolboxRuntimeException('Standard path to the blacklist file not exist.'); 156 | } 157 | } 158 | return $this; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/MxToolbox/DataGrid/MxToolboxDataGrid.php: -------------------------------------------------------------------------------- 1 | string '', 28 | * ['blPositive'] => boolean , 29 | * ['blPositiveResult'] => array() , 30 | * ['blResponse'] => boolean , 31 | * ['blQueryTime'] => boolean 32 | * )); 33 | * 34 | * @var array for dnsbl tests 35 | */ 36 | protected $testStructure; 37 | /** @var NetworkTools object */ 38 | private $netTool; 39 | /** @var BlacklistsHostnameFile object */ 40 | private $fileSys; 41 | 42 | /** 43 | * MxToolboxDataGrid constructor. 44 | * @param BlacklistsHostnameFile $fileSys 45 | * @param NetworkTools $netTool 46 | */ 47 | public function __construct(BlacklistsHostnameFile $fileSys, NetworkTools $netTool) 48 | { 49 | $this->fileSys = $fileSys; 50 | $this->netTool = $netTool; 51 | } 52 | 53 | /** 54 | * Return complete array with tests 55 | * @return array 56 | * @throws MxToolboxLogicException 57 | */ 58 | public function &getTestResultArray() 59 | { 60 | if ($this->isArrayInitialized($this->testStructure)) 61 | return $this->testStructure; 62 | throw new MxToolboxLogicException(sprintf('Array is empty in %s\%s(), set first test results array.', get_class(), __FUNCTION__)); 63 | } 64 | 65 | /** 66 | * Load alive blacklists and create test array. 67 | * @see BlacklistsHostnameFile::loadBlacklistsFromFile() 68 | * @param array $blacklistHostNames - optional (you may use your own blacklist array, default NULL) 69 | * @return $this 70 | * @throws MxToolboxRuntimeException; 71 | * @throws MxToolboxLogicException; 72 | */ 73 | public function buildBlacklistHostNamesArray($blacklistHostNames = NULL) 74 | { 75 | if (is_null($blacklistHostNames) || !is_array($blacklistHostNames)) { 76 | $this->fileSys->loadBlacklistsFromFile('blacklistsAlive.txt'); 77 | $this->setTestResultArray($this->fileSys->getBlacklistsHostNames(), true); 78 | return $this; 79 | } 80 | // build user own blacklist array 81 | $this->setTestResultArray($blacklistHostNames, false, true); 82 | return $this; 83 | } 84 | 85 | /** 86 | * Load default blacklist and check all for alive 87 | * @see BlacklistsHostnameFile::makeAliveBlacklistFile 88 | * @return $this 89 | * @throws MxToolboxLogicException 90 | * @throws MxToolboxRuntimeException 91 | */ 92 | public function buildNewBlacklistHostNames() 93 | { 94 | $this->fileSys->loadBlacklistsFromFile('blacklists.txt'); 95 | // set default blacklists to $testResults 96 | $this->setTestResultArray($this->fileSys->getBlacklistsHostNames(), false); 97 | // set only alive host names 98 | $this->netTool->setDnsblResponse($this->getTestResultArray()); 99 | // check all for alive 100 | $this->fileSys->makeAliveBlacklistFile($this->getTestResultArray()); 101 | // build array from blacklistAlive file 102 | $this->buildBlacklistHostNamesArray(); 103 | return $this; 104 | } 105 | 106 | /** 107 | * Build the array for check DNSBLs 108 | * @param array $blacklistHostNamesArray 109 | * @param bool $alive TRUE is set default blResponse (usually load from alive file) 110 | * @param bool $ownBlacklist 111 | * @return $this 112 | * @throws MxToolboxLogicException 113 | */ 114 | protected function setTestResultArray($blacklistHostNamesArray, $alive = true, $ownBlacklist = false) 115 | { 116 | if ($this->isArrayInitialized($blacklistHostNamesArray)) { 117 | $this->testStructure = array(); 118 | foreach ($blacklistHostNamesArray as $index => $blackList) { 119 | $this->testStructure[$index]['blHostName'] = $blackList; 120 | $this->testStructure[$index]['blPositive'] = false; 121 | $this->testStructure[$index]['blPositiveResult'] = array(); 122 | $this->testStructure[$index]['blResponse'] = $alive; 123 | if ($ownBlacklist) 124 | $this->testStructure[$index]['blResponse'] = $this->netTool->isDnsblResponse($blackList); 125 | $this->testStructure[$index]['blQueryTime'] = false; 126 | } 127 | unset($blackList); 128 | return $this; 129 | } 130 | throw new MxToolboxLogicException(sprintf('Input parameter is empty or is not a array in %s\%s()', get_class(), __FUNCTION__)); 131 | } 132 | 133 | /** 134 | * Clean previous results, reinitialize array 135 | * @param bool $checkResponse - FALSE is faster but without check DNSBL response 136 | * @return $this 137 | * @throws MxToolboxLogicException 138 | */ 139 | public function cleanPrevResults($checkResponse = true) 140 | { 141 | if ($this->isArrayInitialized($this->testStructure)) { 142 | foreach ($this->testStructure as $index => $blackList) { 143 | // here is default true because blacklist is loaded from alive file 144 | $this->testStructure[$index]['blResponse'] = true; 145 | if ($checkResponse) 146 | $this->testStructure[$index]['blResponse'] = $this->netTool->isDnsblResponse($this->testStructure[$index]['blHostName']); 147 | $this->testStructure[$index]['blPositive'] = false; 148 | $this->testStructure[$index]['blPositiveResult'] = array(); 149 | $this->testStructure[$index]['blQueryTime'] = false; 150 | } 151 | return $this; 152 | } 153 | throw new MxToolboxLogicException(sprintf('Array is empty in %s\%s(), set first test results array.', get_class(), __FUNCTION__)); 154 | } 155 | 156 | /** 157 | * Simple check if var is a array and non empty 158 | * @param array $anyArray 159 | * @return bool 160 | */ 161 | protected function isArrayInitialized($anyArray) 162 | { 163 | if (is_array($anyArray) && count($anyArray) > 0) 164 | return true; 165 | return false; 166 | } 167 | } -------------------------------------------------------------------------------- /tests/MxToolbox/MxToolboxTest.php: -------------------------------------------------------------------------------- 1 | basicConfigureMxToolbox($mxt); 23 | unset($mxt); 24 | } 25 | 26 | /** 27 | * @expectedException \MxToolbox\Exceptions\MxToolboxLogicException 28 | */ 29 | public function testSetDnsResolver() 30 | { 31 | $mxt = new MxToolbox(); 32 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', ''); 33 | unset($mxt); 34 | } 35 | 36 | /** 37 | * test domain information like PTR record, Domain name, MX record 38 | */ 39 | public function testDomainInformation() 40 | { 41 | $mxt = new MxToolbox(); 42 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 43 | $this->assertFalse($mxt->getDomainInformation('8.8.8.83')); 44 | $dnsRecords = $mxt->getDomainInformation('8.8.8.8'); 45 | $this->assertInternalType('array', $dnsRecords); 46 | $this->assertInternalType('array', $dnsRecords['mxRecords']); 47 | $this->assertEquals(4, count($dnsRecords)); 48 | } 49 | 50 | /** 51 | * mail server diagnostics 52 | */ 53 | public function testSmtpDiagnostics() 54 | { 55 | $mxt = new MxToolbox(); 56 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 57 | $info = $mxt->getSmtpDiagnosticsInfo( 58 | '194.8.253.5', 59 | 'best-hosting.cz', 60 | 'mxtool@best-hosting.cz', 61 | 'test@example.com' 62 | ); 63 | $this->assertInternalType('array', $info); 64 | } 65 | 66 | /** 67 | * test setBlacklist() 68 | */ 69 | public function testSetBlacklistsDefaultAndOwn() 70 | { 71 | $mxt = new MxToolbox(); 72 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 73 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->setBlacklists()); 74 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 75 | $myBlacklist = array( 76 | 0 => 'zen.spamhaus.org', 77 | 1 => 'xbl.spamhaus.org' 78 | ); 79 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->setBlacklists($myBlacklist)); 80 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 81 | $this->assertCount(2, $mxt->getBlacklistsArray()); 82 | } 83 | 84 | /** 85 | * @expectedException \MxToolbox\Exceptions\MxToolboxLogicException 86 | */ 87 | public function testGetBlacklistsArrayException() 88 | { 89 | $mxt = new MxToolbox(); 90 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 91 | $mxt->getBlacklistsArray(); 92 | } 93 | 94 | /** 95 | * test get blacklist array 96 | */ 97 | public function testGetBlacklistsArray() 98 | { 99 | $mxt = new MxToolbox(); 100 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 101 | $mxt->setBlacklists(); 102 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 103 | } 104 | 105 | /** 106 | * test dig path, file exist, string contain dig 107 | */ 108 | public function testGetDigPath() 109 | { 110 | $mxt = new MxToolbox(); 111 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 112 | $this->assertInternalType('string', $mxt->getDigPath()); 113 | $this->assertFileExists($mxt->getDigPath()); 114 | $this->assertContains('dig', $mxt->getDigPath()); 115 | } 116 | 117 | /** 118 | * test get resolvers array 119 | */ 120 | public function testGetDnsResolvers() 121 | { 122 | $mxt = new MxToolbox(); 123 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 124 | $this->assertInternalType('array', $mxt->getDnsResolvers()); 125 | 126 | } 127 | 128 | /** 129 | * test refresh alive blacklist file 130 | */ 131 | public function testUpdateAliveBlackFile() 132 | { 133 | $mxt = new MxToolbox(); 134 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 135 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->updateAliveBlacklistFile()); 136 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 137 | } 138 | 139 | /** 140 | * test check IP address in DNSBL and clean results for next test 141 | */ 142 | public function testCheckIpDnsblCleanArray() 143 | { 144 | $mxt = new MxToolbox(); 145 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 146 | $this->assertInstanceOf('MxToolbox\MxToolbox', 147 | $mxt 148 | ->setBlacklists() 149 | ->checkIpAddressOnDnsbl('127.0.0.2') 150 | ); 151 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 152 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->cleanBlacklistArray()); 153 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 154 | } 155 | 156 | /** 157 | * test check IP address in DNSBL with python multiprocess script and clean results for next test 158 | */ 159 | public function testCheckIpDnsblMultiprocess() 160 | { 161 | $mxt = new MxToolbox(); 162 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 163 | $this->assertInstanceOf('MxToolbox\MxToolbox', 164 | $mxt 165 | ->setBlacklists() 166 | ->checkIpAddressOnDnsbl('127.0.0.2',true) 167 | ); 168 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 169 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->cleanBlacklistArray()); 170 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 171 | } 172 | 173 | /** 174 | * @expectedException \MxToolbox\Exceptions\MxToolboxLogicException 175 | */ 176 | public function testCheckIpDnsblCleanArrayException() 177 | { 178 | $mxt = new MxToolbox(); 179 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 180 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->checkIpAddressOnDnsbl('8.8.8.8')); 181 | } 182 | 183 | /** 184 | * test user defined path to the blacklist files 185 | */ 186 | public function testUserDefinedBlacklistFilePath() 187 | { 188 | $mxt = new MxToolbox(); 189 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 190 | $mxt->setBlacklistFilePath(dirname(__FILE__) . '/../../vendor/mxtoolbox-blacklists/mxtoolbox-blacklists/'); 191 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->setBlacklists()); 192 | $this->assertInstanceOf('MxToolbox\MxToolbox', $mxt->checkIpAddressOnDnsbl('8.8.8.8')); 193 | $this->assertInternalType('array', $mxt->getBlacklistsArray()); 194 | } 195 | 196 | /** 197 | * Test user defined bad path to the blacklist files 198 | * @expectedException \MxToolbox\Exceptions\MxToolboxRuntimeException 199 | */ 200 | public function testUserDefinedBlacklistFilePathException() 201 | { 202 | $mxt = new MxToolbox(); 203 | $this->basicConfigureMxToolbox($mxt, '/usr/bin/dig', '8.8.8.8'); 204 | $mxt->setBlacklistFilePath('./foo/'); 205 | $mxt->setBlacklists(); 206 | } 207 | 208 | /** 209 | * Basic configuration 210 | * @param MxToolbox $mxt 211 | * @param string|bool $dns 212 | * @param string|bool $dig 213 | */ 214 | private function basicConfigureMxToolbox(MxToolbox $mxt, $dig = false, $dns = false) 215 | { 216 | $mxt->setDig($dig)->setDnsResolver($dns); 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /src/MxToolbox/MxToolbox.php: -------------------------------------------------------------------------------- 1 | netTool = $this->createServiceNetworkTool(); 41 | $this->fileSys = $this->createServiceBlacklistsHostnameFile(); 42 | $this->dataGrid = $this->createServiceMxToolboxDataGrid(); 43 | } 44 | 45 | /** 46 | * Set dig path - required, etc: /usr/bin/dig 47 | * @param string $digPath 48 | * @return $this 49 | * @throws MxToolboxLogicException 50 | */ 51 | public function setDig($digPath) 52 | { 53 | $this->netTool->setDigPath($digPath); 54 | return $this; 55 | } 56 | 57 | /** 58 | * Set path to the blacklist files - optional 59 | * @param string $path 60 | * @return $this 61 | */ 62 | public function setBlacklistFilePath($path) 63 | { 64 | $this->fileSys->setBlacklistFilePath($path); 65 | return $this; 66 | } 67 | 68 | /** 69 | * Set DNS resolver IP address - required (support for multiples push) 70 | * @param string $addr IP address 71 | * @return $this 72 | * @throws MxToolboxLogicException 73 | */ 74 | public function setDnsResolver($addr) 75 | { 76 | $this->netTool->setDnsResolverIP($addr); 77 | return $this; 78 | } 79 | 80 | /** 81 | * Initialize blacklist array from file or custom array - optional 82 | * @param array $ownBlacklist optional, default nothing 83 | * @return $this 84 | * @throws MxToolboxRuntimeException 85 | * @throws MxToolboxLogicException 86 | */ 87 | public function setBlacklists($ownBlacklist = null) 88 | { 89 | try { 90 | $this->dataGrid->buildBlacklistHostNamesArray($ownBlacklist); 91 | return $this; 92 | } catch (MxToolboxRuntimeException $e) { 93 | if ($e->getCode() == 400) { 94 | $this->dataGrid->buildNewBlacklistHostNames(); 95 | return $this; 96 | } 97 | return $e; 98 | } 99 | } 100 | 101 | /** 102 | * Get DNS resolvers 103 | * @return array 104 | */ 105 | public function getDnsResolvers() 106 | { 107 | return $this->netTool->getDnsResolvers(); 108 | } 109 | 110 | /** 111 | * Get DIG path 112 | * @return string 113 | */ 114 | public function getDigPath() 115 | { 116 | return $this->netTool->getDigPath(); 117 | } 118 | 119 | /** 120 | * Get blacklists array. 121 | * 122 | * array( array( 123 | * ['blHostName'] => string '', 124 | * ['blPositive'] => boolean , 125 | * ['blPositiveResult'] => array() , 126 | * ['blResponse'] => boolean , 127 | * ['blQueryTime'] => boolean 128 | * )); 129 | * @return array 130 | * @throws MxToolboxLogicException 131 | */ 132 | public function getBlacklistsArray() 133 | { 134 | return $this->dataGrid->getTestResultArray(); 135 | } 136 | 137 | /** 138 | * Get some additional information about IP address (IP Address, PTR record, Domain name, MX records ) 139 | * 140 | * Return information in array( 141 | * ['ipAddress'] => string, 142 | * ['domainName'] => string, 143 | * ['ptrRecord'] => string, 144 | * ['mxRecords'] => array( 145 | * )); 146 | * @param string $addr IP address or domain name 147 | * @return array|bool - return array or FALSE if no information here. 148 | */ 149 | public function getDomainInformation($addr) 150 | { 151 | $info = $this->netTool->getDomainDetailInfo($addr); 152 | if (count($info) > 0) 153 | return $info; 154 | return false; 155 | } 156 | 157 | /** 158 | * Get SMTP diagnostics information. 159 | * 160 | * Return information in array( 161 | * ['rDnsMismatch']['state'] => boolean, 162 | * ['rDnsMismatch']['info'] => string, 163 | * ['validHostname']['state'] => boolean, 164 | * ['validHostname']['info'] => string, 165 | * ['bannerCheck']['state'] => boolean, 166 | * ['bannerCheck']['info'] => string, 167 | * ['tls']['state'] => boolean, 168 | * ['tls']['info'] => string, 169 | * ['openRelay']['state'] => boolean, 170 | * ['openRelay']['info'] => string, 171 | * ['errors']['state'] => boolean, 172 | * ['errors']['info'] => string, 173 | * ['allResponses'] => array( 174 | * [connection] => string, 175 | * [ehlo] => array(), 176 | * [mailFrom] => string, 177 | * [rcptTo] => string, 178 | * ), 179 | * ); 180 | * 181 | * @param string $addr IP address or domain name 182 | * @param string $myHostName real HostName of the server where script is running (must be resolved to IP address) 183 | * @param string $mailFrom Any testing mail address (domain is same as hostname) 184 | * @param string $mailRcptTo non exist email address as test@example.com 185 | * @return array 186 | * @throws MxToolboxRuntimeException 187 | * @throws MxToolboxLogicException 188 | */ 189 | public function getSmtpDiagnosticsInfo($addr, $myHostName, $mailFrom, $mailRcptTo) 190 | { 191 | return $this 192 | ->createServiceSmtpServerChecks($addr, $myHostName, $mailFrom, $mailRcptTo) 193 | ->getSmtpServerDiagnostic(); 194 | } 195 | 196 | /** 197 | * Refresh alive blacklists host names in static file (/blacklistsAlive.txt) 198 | * @return $this 199 | * @throws MxToolboxRuntimeException 200 | * @throws MxToolboxLogicException 201 | */ 202 | public function updateAliveBlacklistFile() 203 | { 204 | $this->fileSys->deleteAliveBlacklist(); 205 | $this->setBlacklists(); 206 | return $this; 207 | } 208 | 209 | /** 210 | * Clean blacklist array from previous search 211 | * 212 | * @param bool $checkResponse - default TRUE, FALSE is faster but without check DNSBL response 213 | * @return $this 214 | * @throws MxToolboxLogicException 215 | */ 216 | public function cleanBlacklistArray($checkResponse = true) 217 | { 218 | $this->dataGrid->cleanPrevResults($checkResponse); 219 | return $this; 220 | } 221 | 222 | /** 223 | * Check IP address or domain name on all DNSBL servers 224 | * @param string $addr ip address or domain name 225 | * @param boolean $quick (default false) for run quick multiprocess (Python >=2.7 required!) 226 | * @return $this 227 | * @throws MxToolboxRuntimeException 228 | * @throws MxToolboxLogicException 229 | */ 230 | public function checkIpAddressOnDnsbl($addr, $quick = false) 231 | { 232 | if ($quick) { 233 | $quickDig = $this->createServiceQuickDig(); 234 | $quickDig 235 | ->getJsonFromDigMultiprocess($addr, $this->dataGrid->getTestResultArray()) 236 | ->parseDataFromMultiprocessing(); 237 | return $this; 238 | } 239 | $this->netTool->checkAllDnsbl($addr, $this->dataGrid->getTestResultArray()); 240 | return $this; 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /src/MxToolbox/NetworkTools/NetworkTools.php: -------------------------------------------------------------------------------- 1 | digParser = new DigQueryParser(); 48 | } 49 | 50 | /** 51 | * Push one IP address of a DNS resolver to the resolvers list 52 | * (tcp port 53 must be open) 53 | * (UDP sockets will sometimes appear to have opened without an error, even if the remote host is unreachable.) 54 | * (DNS Works On Both TCP and UDP ports) 55 | * @param string $addr 56 | * @return $this 57 | * @throws MxToolboxLogicException 58 | */ 59 | public function setDnsResolverIP($addr) 60 | { 61 | if ($this->validateIPAddress($addr) && $fss = @fsockopen('tcp://' . $addr, 53, $errNo, $errStr, 5)) { 62 | fclose($fss); 63 | $this->dnsResolvers[] = $addr; 64 | return $this; 65 | } 66 | throw new MxToolboxLogicException('DNS Resolver: tcp://' . $addr . ':53 can\'t open listening socket.'); 67 | } 68 | 69 | /** 70 | * Set path to dig utility, etc: '/usr/bin/dig' 71 | * @param string $digPath 72 | * @return $this 73 | * @throws MxToolboxLogicException 74 | */ 75 | public function setDigPath($digPath) 76 | { 77 | if (!empty($digPath) && is_file($digPath)) { 78 | $this->digPath = $digPath; 79 | return $this; 80 | } 81 | throw new MxToolboxLogicException('DIG file does not exist!'); 82 | } 83 | 84 | /** 85 | * Set 'blResponse' in testResult array on true if is dnsbl hostname alive 86 | * @param array $testResults 87 | * @return $this 88 | */ 89 | public function setDnsblResponse(&$testResults) 90 | { 91 | foreach ($testResults as $key => $val) { 92 | if ($this->isDnsblResponse($val['blHostName'])) 93 | $testResults[$key]['blResponse'] = true; 94 | } 95 | return $this; 96 | } 97 | 98 | /** 99 | * Get Dns resolvers array 100 | * @return array 101 | */ 102 | public function getDnsResolvers() 103 | { 104 | return $this->dnsResolvers; 105 | } 106 | 107 | /** 108 | * Get DIG path 109 | * @return string 110 | */ 111 | public function getDigPath() 112 | { 113 | return $this->digPath; 114 | } 115 | 116 | /** 117 | * Check DNSBL PTR Record 118 | * TODO: ipv6 support 119 | * @param string $addr 120 | * @param string $dnsResolver 121 | * @param string $blackList 122 | * @param string $record 'A,TXT,PTR,AAAA?', default 'A' 123 | * @return string 124 | */ 125 | public function getDigResult($addr, $dnsResolver, $blackList, $record = 'A') 126 | { 127 | $checkResult = shell_exec($this->digPath . ' @' . $dnsResolver . 128 | ' +time=2 +tries=2 +nocmd ' . $this->reverseIP($addr) . '.' . $blackList . ' ' . $record); 129 | return $checkResult; 130 | } 131 | 132 | /** 133 | * Get some additional information about ip address as PTR,Domain name, MX records. 134 | * 135 | * Structure: 136 | * array( 137 | * ['ipAddress'] => string 138 | * ['domainName'] => string, 139 | * ['ptrRecord'] => string, 140 | * ['mxRecords'] => array( 141 | * )); 142 | * @param string $addr IP address 143 | * @return array 144 | */ 145 | public function getDomainDetailInfo($addr) 146 | { 147 | $this->setDigPath($this->digPath); 148 | $info = array(); 149 | if ($this->checkExistPTR($addr)) { 150 | $info['ipAddress'] = $this->ipAddress; 151 | $info['domainName'] = $this->domainName; 152 | $info['ptrRecord'] = $this->ptrRecord; 153 | $info['mxRecords'] = array(); 154 | if ($this->getMxRecords($this->domainName)) 155 | $info['mxRecords'] = $this->mxRecords; 156 | } 157 | return $info; 158 | } 159 | 160 | /** 161 | * Get IP address from domain name 162 | * @param string $addr Domain name or IP address 163 | * @return string ip address 164 | */ 165 | public function getIpAddressFromDomainName($addr) { 166 | 167 | if ($this->isDomainName($addr) && 168 | $this->ipValidator($ipAddress = gethostbyname($addr))) 169 | return $ipAddress; 170 | return $addr; 171 | } 172 | 173 | /** 174 | * Check one hostname for response on 127.0.0.2 175 | * @param string $host 176 | * @return bool 177 | */ 178 | public function isDnsblResponse($host) 179 | { 180 | $digOutput = $this->getDigResult('127.0.0.2', $this->getRandomDNSResolverIP(), $host, 'A'); 181 | if ($this->digParser->isNoError($digOutput)) 182 | return true; 183 | return false; 184 | } 185 | 186 | /** 187 | * Check all (use only alive rBLS - fast check!) 188 | * @param string $addr IP address or domain name 189 | * @param array $testResult 190 | * @return $this 191 | * @throws MxToolboxLogicException 192 | * @throws MxToolboxRuntimeException 193 | */ 194 | public function checkAllDnsbl($addr, &$testResult) 195 | { 196 | $this->ipValidator($addr); 197 | $this->ipAddress = $this->getIpAddressFromDomainName($addr); 198 | if (count($testResult) > 0) { 199 | foreach ($testResult as &$blackList) { 200 | $digOutput = $this->getDigResult( 201 | $this->ipAddress, 202 | $this->getRandomDNSResolverIP(), 203 | $blackList['blHostName'], 'TXT' 204 | ); 205 | 206 | if ($this->digParser->isNoError($digOutput)) { 207 | $blackList['blPositive'] = true; 208 | $blackList['blPositiveResult'] = $this->digParser->getPositiveUrlAddresses($digOutput); 209 | } 210 | 211 | $blackList['blQueryTime'] = $this->digParser->getQueryTime($digOutput); 212 | } 213 | return $this; 214 | } 215 | throw new MxToolboxRuntimeException(sprintf('Array is empty for dig checks in: %s\%s.', get_class(), __FUNCTION__)); 216 | } 217 | 218 | /** 219 | * Reverse IP address 192.168.1.254 -> 254.1.168.192 220 | * @param string $addr 221 | * @return string 222 | */ 223 | public function reverseIP($addr) 224 | { 225 | $revIpAddr = explode(".", $addr); 226 | return $revIpAddr[3] . '.' . $revIpAddr[2] . '.' . $revIpAddr[1] . '.' . $revIpAddr[0]; 227 | } 228 | 229 | /** 230 | * Check if string is valid IPv4 address or domain 231 | * @param string $addr 232 | * @return boolean 233 | */ 234 | public function ipValidator($addr) 235 | { 236 | if ($this->isDomainName($addr)) { 237 | if ($this->validateIPAddress(gethostbyname($addr))) 238 | return true; 239 | return false; 240 | } 241 | 242 | if ($this->validateIPAddress($addr)) 243 | return true; 244 | return false; 245 | } 246 | 247 | /** 248 | * Check if a string represents domain name 249 | * @param string $addr 250 | * @return boolean 251 | */ 252 | public function isDomainName($addr) 253 | { 254 | if (preg_match("/^[a-zA-Z0-9.\-]{2,256}\.[a-z]{2,6}$/", $addr)) 255 | return true; 256 | return false; 257 | } 258 | 259 | /** 260 | * Validate if string is valid IP address 261 | * @param string $addr 262 | * @return boolean 263 | */ 264 | private function validateIPAddress($addr) 265 | { 266 | if (filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) 267 | return true; 268 | throw new MxToolboxLogicException($addr . " isn't correct IP address or domain name."); 269 | } 270 | 271 | /** 272 | * Get random DNS IP address from array 273 | * @return string '' 274 | * @throws MxToolboxLogicException 275 | */ 276 | public function getRandomDNSResolverIP() 277 | { 278 | if (!count($this->dnsResolvers) > 0) 279 | throw new MxToolboxLogicException('No DNS resolver here!'); 280 | return $this->dnsResolvers[array_rand($this->dnsResolvers, 1)]; 281 | } 282 | 283 | /** 284 | * Get MX records from domain name 285 | * @param string $hostName 286 | * @return boolean 287 | */ 288 | private function getMxRecords($hostName) 289 | { 290 | if (preg_match("/^[a-zA-Z0-9.\-]{2,256}\.[a-z]{2,6}$/", trim($hostName))) { 291 | $ptr = dns_get_record($hostName, DNS_MX); 292 | if (isset($ptr[0]['target'])) { 293 | $mxRecords = array(); 294 | foreach ($ptr as $mx) 295 | $mxRecords[] = $mx['target']; 296 | $this->mxRecords = $mxRecords; 297 | return true; 298 | } 299 | } 300 | return false; 301 | } 302 | 303 | /** 304 | * Check if IP address have a PTR record 305 | * @param string $addr IP address 306 | * @return boolean 307 | */ 308 | private function checkExistPTR($addr) 309 | { 310 | if (!$this->ipValidator($addr)) 311 | return false; 312 | $this->ipAddress = $this->getIpAddressFromDomainName($addr); 313 | $digResponse = $this->getDigResult($this->ipAddress, $this->getRandomDNSResolverIP(), 'in-addr.arpa', 'PTR'); 314 | if ($this->digParser->isNoError($digResponse)) { 315 | $ptr = dns_get_record($this->reverseIP($this->ipAddress) . '.in-addr.arpa.', DNS_PTR); 316 | if (isset($ptr[0]['target'])) { 317 | $regs = array(); 318 | $this->ptrRecord = $ptr[0]['target']; 319 | if (preg_match('/(?P[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $ptr[0]['target'], $regs)) 320 | $this->domainName = $regs['domain']; 321 | return true; 322 | } 323 | } 324 | return false; 325 | } 326 | 327 | } 328 | -------------------------------------------------------------------------------- /src/MxToolbox/NetworkTools/SmtpServerChecks.php: -------------------------------------------------------------------------------- 1 | netTool = $netTool; 66 | $this->netTool->ipValidator($addr); 67 | 68 | if (!$this->isEmail($mailFrom) || !$this->isEmail($mailRcptTo)) 69 | throw new MxToolboxLogicException('Non valid email format.'); 70 | 71 | if (empty($myHostName) || !$this->netTool->ipValidator($myHostName)) 72 | throw new MxToolboxLogicException('Missing or bad argument myHostName.'); 73 | 74 | $this->addr = $this->netTool->getIpAddressFromDomainName($addr); 75 | $this->myHostName = $myHostName; 76 | $this->emailFrom = $mailFrom; 77 | $this->emailRcptTo = $mailRcptTo; 78 | $this->setFinalResultsArray(); 79 | } 80 | 81 | /** 82 | * Get SMTP diagnostics information. 83 | * 84 | * Return information in array( 85 | * ['rDnsMismatch']['state'] => boolean, 86 | * ['rDnsMismatch']['info'] => string, 87 | * ['validHostname']['state'] => boolean, 88 | * ['validHostname']['info'] => string, 89 | * ['bannerCheck']['state'] => boolean, 90 | * ['bannerCheck']['info'] => string, 91 | * ['tls']['state'] => boolean, 92 | * ['tls']['info'] => string, 93 | * ['openRelay']['state'] => boolean, 94 | * ['openRelay']['info'] => string, 95 | * ['errors']['state'] => boolean, 96 | * ['errors']['info'] => string, 97 | * ['allResponses'] => array() 98 | * ['allResponses'] => array( 99 | * [connection] => string, 100 | * [ehlo] => array(), 101 | * [mailFrom] => string, 102 | * [rcptTo] => string, 103 | * ), 104 | * ); 105 | * 106 | * @return array 107 | */ 108 | public function getSmtpServerDiagnostic() 109 | { 110 | if (!$this->setSmtpConnect($this->addr)) { 111 | $this->closeSmtpConnection(); 112 | return $this->finalResults; 113 | } 114 | $this 115 | ->setEhloResponse() 116 | ->setFromResponse() 117 | ->setRcptToResponse() 118 | ->closeSmtpConnection() 119 | ->parseResults(); 120 | return $this->finalResults; 121 | } 122 | 123 | /** 124 | * Connect to the SMTP server 125 | * @param string $addr IP address for test 126 | * @return boolean 127 | */ 128 | private function setSmtpConnect($addr) 129 | { 130 | $this->smtpConnection = @stream_socket_client($addr . ':' . $this->smtpPort, $errno, $errstr, 131 | $this->connTimeout, STREAM_CLIENT_CONNECT); 132 | if (is_resource($this->smtpConnection)) { 133 | stream_set_timeout($this->smtpConnection, $this->connTimeout); 134 | $this->smtpResponses['connection'] = $this->readCommand(); 135 | $info = stream_get_meta_data($this->smtpConnection); 136 | if ($info['timed_out']) { 137 | $this->finalResults['errors']['info'] = 'SMTP command (waiting timeout).'; 138 | return false; 139 | } 140 | $this->finalResults['errors']['state'] = false; 141 | return true; 142 | } 143 | $this->finalResults['errors']['info'] = 'Unable to connect to ' . $addr . ':25 (Timeout | No route to host).'; 144 | return false; 145 | } 146 | 147 | /** 148 | * Starting the conversation. Server wants to use the extended SMTP (ESMTP) protocol. 149 | * @return $this 150 | * @throws MxToolboxRuntimeException 151 | */ 152 | private function setEhloResponse() 153 | { 154 | if (is_resource($this->smtpConnection)) { 155 | $this->writeCommand('EHLO', $this->myHostName); 156 | $this->smtpResponses['ehlo'] = array_filter(explode(self::CRLF, $this->readCommand())); 157 | return $this; 158 | } 159 | throw new MxToolboxRuntimeException('Invalid connection'); 160 | } 161 | 162 | /** 163 | * Set MAIL FROM response 164 | * @return $this 165 | */ 166 | private function setFromResponse() 167 | { 168 | if (is_resource($this->smtpConnection)) { 169 | $this->writeCommand('MAIL FROM:', $this->emailFrom); 170 | $this->smtpResponses['mailFrom'] = $this->readCommand(); 171 | return $this; 172 | } 173 | throw new MxToolboxRuntimeException('Invalid connection'); 174 | } 175 | 176 | /** 177 | * Set RCPT TO response 178 | * @return $this 179 | */ 180 | private function setRcptToResponse() 181 | { 182 | if (is_resource($this->smtpConnection)) { 183 | $this->writeCommand('RCPT TO:', $this->emailFrom); 184 | $this->smtpResponses['rcptTo'] = $this->readCommand(); 185 | return $this; 186 | } 187 | throw new MxToolboxRuntimeException('Invalid connection'); 188 | } 189 | 190 | /** 191 | * Write command to the SMTP stream 192 | * @param string $command 193 | * @param string $param 194 | */ 195 | private function writeCommand($command, $param = '') 196 | { 197 | fwrite($this->smtpConnection, $command . " " . $param . self::CRLF); 198 | } 199 | 200 | /** 201 | * Read command from the SMTP stream 202 | * @return mixed 203 | */ 204 | private function readCommand() 205 | { 206 | return fread($this->smtpConnection, 4096); 207 | } 208 | 209 | /** 210 | * Close the SMTP connection 211 | * @return $this 212 | */ 213 | private function closeSmtpConnection() 214 | { 215 | if (is_resource($this->smtpConnection)) { 216 | fwrite($this->smtpConnection, 'QUIT' . self::CRLF); 217 | fclose($this->smtpConnection); 218 | } 219 | return $this; 220 | } 221 | 222 | /** 223 | * Initialize final results array 224 | */ 225 | private function setFinalResultsArray() 226 | { 227 | $this->finalResults['rDnsMismatch']['state'] = false; 228 | $this->finalResults['rDnsMismatch']['info'] = ''; 229 | $this->finalResults['validHostname']['state'] = false; 230 | $this->finalResults['validHostname']['info'] = ''; 231 | $this->finalResults['bannerCheck']['state'] = false; 232 | $this->finalResults['bannerCheck']['info'] = ''; 233 | $this->finalResults['tls']['state'] = false; 234 | $this->finalResults['tls']['info'] = ''; 235 | // $this->finalResults['connectTime']['state'] = false; 236 | // $this->finalResults['connectTime']['info'] = ''; 237 | $this->finalResults['openRelay']['state'] = true; 238 | $this->finalResults['openRelay']['info'] = ''; 239 | $this->finalResults['errors']['state'] = true; 240 | $this->finalResults['errors']['info'] = ''; 241 | $this->finalResults['allResponses'] = false; 242 | return $this; 243 | } 244 | 245 | /** 246 | * Is string email format 247 | * @param string $email 248 | * @return bool 249 | */ 250 | private function isEmail($email) 251 | { 252 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) 253 | return false; 254 | return true; 255 | } 256 | 257 | 258 | /** 259 | * Parse all responses from the SMTP stream 260 | * @return $this 261 | */ 262 | private function parseResults() 263 | { 264 | $parser = new SmtpDiagnosticParser(); 265 | $info = $this->netTool->getDomainDetailInfo($this->addr); 266 | 267 | // Check TLS available 268 | // Reverse DNS Mismatch: If the A record of the hostname did not match the PTR = TRUE 269 | // Reverse DNS Hostname validation 270 | // Reverse DNS checks in SMTP Banner 271 | // Check open relay 272 | $this 273 | ->checkTls($parser) 274 | ->checkRDnsMismatch($info, $parser) 275 | ->checkValidHostname($info, $parser) 276 | ->checkSmtpBanner($info, $parser) 277 | ->checkOpenRelay($parser); 278 | 279 | // set all responses to final result 280 | if (count($this->smtpResponses) > 0) 281 | $this->finalResults['allResponses'] = $this->smtpResponses; 282 | return $this; 283 | } 284 | 285 | /** 286 | * Check if is TLS supported 287 | * @param SmtpDiagnosticParser $parser 288 | * @return $this 289 | */ 290 | private function checkTls($parser) 291 | { 292 | $this->finalResults['tls']['state'] = $parser->isTls($this->smtpResponses['ehlo']); 293 | $this->finalResults['tls']['info'] = 'OK - TLS is supported.'; 294 | if (!$this->finalResults['tls']['state']) 295 | $this->finalResults['tls']['info'] = 'ERR - TLS is not supported!'; 296 | return $this; 297 | } 298 | 299 | /** 300 | * Check valid hostname 301 | * @param array $info 302 | * @param SmtpDiagnosticParser $parser 303 | * @return $this 304 | */ 305 | private function checkValidHostname($info, SmtpDiagnosticParser $parser) 306 | { 307 | if (isset($info['ptrRecord']) && $this->netTool->isDomainName($info['ptrRecord'])) 308 | $this->finalResults['validHostname']['state'] = $parser->isValidHostname( 309 | $this->smtpResponses['ehlo'], $info['ptrRecord'] 310 | ); 311 | $this->finalResults['validHostname']['info'] = 'OK - rDNS is a valid hostname.'; 312 | if (!$this->finalResults['validHostname']['state']) 313 | $this->finalResults['validHostname']['info'] = 'ERR - rDNS is not a valid hostname!'; 314 | return $this; 315 | 316 | } 317 | 318 | /** 319 | * Check rDNSMismatch 320 | * @param array $info 321 | * @param SmtpDiagnosticParser $parser 322 | * @return $this 323 | */ 324 | private function checkRDnsMismatch($info, SmtpDiagnosticParser $parser) 325 | { 326 | if (isset($info['ptrRecord']) && $this->netTool->isDomainName($info['ptrRecord'])) { 327 | $this->finalResults['rDnsMismatch']['state'] = $parser->isReverseDnsMismatch( 328 | $this->addr, dns_get_record($info['ptrRecord'], DNS_A) 329 | ); 330 | $this->finalResults['rDnsMismatch']['info'] = 'OK - ' . $this->addr . ' resolves to ' . $info['ptrRecord']; 331 | } 332 | if (!$this->finalResults['rDnsMismatch']['state']) 333 | $this->finalResults['rDnsMismatch']['info'] = 'ERR - Cannot resolve address: ' . $this->addr; 334 | return $this; 335 | } 336 | 337 | /** 338 | * Check PTR matches in SMTP banner 339 | * @param array $info 340 | * @param SmtpDiagnosticParser $parser 341 | * @return $this 342 | */ 343 | private function checkSmtpBanner($info, SmtpDiagnosticParser $parser) 344 | { 345 | if (isset($info['ptrRecord']) && $this->netTool->isDomainName($info['ptrRecord'])) { 346 | $this->finalResults['bannerCheck']['state'] = $parser->isReverseDnsInBanner( 347 | $this->smtpResponses['connection'], $info['ptrRecord'] 348 | ); 349 | $this->finalResults['bannerCheck']['info'] = 'OK - Reverse DNS matches SMTP banner.'; 350 | } 351 | if (!$this->finalResults['bannerCheck']['state']) 352 | $this->finalResults['bannerCheck']['info'] = 'ERR - Reverse DNS does not match SMTP banner!'; 353 | return $this; 354 | } 355 | 356 | /** 357 | * Check open relay access 358 | * @param SmtpDiagnosticParser $parser 359 | * @return $this 360 | */ 361 | private function checkOpenRelay(SmtpDiagnosticParser $parser) 362 | { 363 | if (isset($this->smtpResponses['rcptTo'])) 364 | $this->finalResults['openRelay']['state'] = $parser->isOpenRelay($this->smtpResponses['rcptTo'][0]); 365 | $this->finalResults['openRelay']['info'] = 'OK - Relay access denied.'; 366 | if (!$this->finalResults['openRelay']['state']) 367 | $this->finalResults['openRelay']['info'] = 'ERR - Relay access open!'; 368 | return $this; 369 | } 370 | 371 | } 372 | 373 | --------------------------------------------------------------------------------