From a3df0887c9596f406692ad78e0c73e722807ef72 Mon Sep 17 00:00:00 2001 From: kristuff Date: Sun, 17 Jan 2021 20:56:12 +0100 Subject: [PATCH 1/5] v0.9.8 **Changed** - internal refactoring, better errors handling - created a link in composer json to deploy bin file (support for install this as a lib in another or blank project) --- bin/abuseipdb | 1340 +---------------------------------- composer.json | 9 +- config/self_ips.sample.json | 2 +- src/AbuseIPDBClient.php | 895 +++++++++++++++++++++++ src/ShellUtils.php | 298 ++++++++ src/UtilsTrait.php | 96 +++ 6 files changed, 1303 insertions(+), 1337 deletions(-) create mode 100644 src/AbuseIPDBClient.php create mode 100644 src/ShellUtils.php create mode 100644 src/UtilsTrait.php diff --git a/bin/abuseipdb b/bin/abuseipdb index ad7e0df..8756127 100644 --- a/bin/abuseipdb +++ b/bin/abuseipdb @@ -15,1345 +15,19 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. * - * @version 0.9.7 + * @version 0.9.8 * @copyright 2020-2021 Kristuff */ -namespace Kristuff\AbuseIPDB; require_once realpath(__DIR__) .'/../vendor/autoload.php'; -use Kristuff\AbuseIPDB\ApiHandler; -use Kristuff\Mishell\Console; +use Kristuff\AbuseIPDB\AbuseIPDBClient; -// define arguments and start our app -$arguments = getopt( - 'GLBK:C:d:R:c:m:l:pE:V:hvs:', - ['config', 'list', 'blacklist', 'check:', 'check-block:', 'days:', 'report:', 'categories:', 'message:', 'limit:', 'plaintext', 'clear:','bulk-report:', 'help', 'verbose', 'score:'] +AbuseIPDBClient::start( + getopt( + AbuseIPDBClient::SHORT_ARGUMENTS, + AbuseIPDBClient::LONG_ARGUMENTS + ) ); -AbuseIPDBcli::start($arguments); - -/** - * Class AbuseIPDB - * - * The main cli program - */ -class AbuseIPDBcli -{ - /** - * @var string $version - */ - private static $version = 'v0.9.7'; - - /** - * @var ApiHandler $api - */ - private static $api = null; - - /** - * @var string $keyPath - */ - private static $keyPath = __DIR__ .'/../config/key.json'; - - /** - * The entry point of our app - * - * @access public - * @static - * @param array $arguments - * - * @return void - */ - public static function start($arguments) - { - // check for install - if ( !self::checkForInstall() ){ - Console::log(); - self::error('Key file missing.'); - exit(1); - } - - // Create a new instance of \ApiHandler with the given config file - try { - self::$api = self::fromConfigFile(self::$keyPath); - } catch (\Exception $e) { - // done, we clear previous the message. Makes sure we clear the whole line with long string - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - // required at least one valid argument - if ( empty($arguments)){ - Console::log(); - self::error('No valid arguments given. Run abuseipdb --help to get help.'); - self::printFooter(); - - exit(1); - } - - // prints help ? - if (self::inArguments($arguments, 'h', 'help')){ - self::printBanner(); - self::printHelp(); - exit(0); - } - - // prints config ? - if (self::inArguments($arguments, 'G', 'config')){ - self::printConfig(); - exit(0); - } - - // prints catgeories ? - if (self::inArguments($arguments, 'L', 'list')){ - self::printCategories(); - exit(0); - } - - // check request ? - if (self::inArguments($arguments, 'C', 'check')){ - self::checkIP($arguments); - exit(0); - } - - // check-block request ? - if (self::inArguments($arguments, 'K', 'check-block')){ - self::checkBlock($arguments); - exit(0); - } - - // report request ? - if (self::inArguments($arguments, 'R', 'report')){ - self::reportIP($arguments); - exit(0); - } - - // report request ? - if (self::inArguments($arguments, 'V', 'bulk-report')){ - self::bulkReport($arguments); - exit(0); - } - - // report request ? - if (self::inArguments($arguments, 'B', 'blacklist')){ - self::getBlacklist($arguments); - exit(0); - } - - // report request ? - if (self::inArguments($arguments, 'E', 'clear')){ - self::clearIP($arguments); - exit(0); - } - - // no valid arguments given, close program - Console::log(); - self::error('invalid arguments. Run abuseipdb --help to get help.'); - self::printFooter(); - exit(1); - } - - /** - * Get a new instance of ApiHandler with config stored in a Json file - * - * @access public - * @static - * @param string $configPath The configuration file path - * - * @return \Kristuff\AbuseIPDB\ApiHandler - * @throws \InvalidArgumentException If the given file does not exist - * @throws \Kristuff\AbuseIPDB\InvalidPermissionException If the given file is not readable - */ - public static function fromConfigFile(string $configPath) - { - - // check file exists - if (!file_exists($configPath) || !is_file($configPath)){ - throw new \InvalidArgumentException('The file [' . $configPath . '] does not exist.'); - } - - // check file is readable - if (!is_readable($configPath)){ - throw new InvalidPermissionException('The file [' . $configPath . '] is not readable.'); - } - - $keyConfig = self::loadJsonFile($configPath); - $selfIps = []; - - // Look for other optional config files in the same directory - $selfIpsConfigPath = pathinfo($configPath, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . 'self_ips.json'; - if (file_exists($selfIpsConfigPath)){ - $selfIps = self::loadJsonFile($selfIpsConfigPath)->self_ips; - } - - $app = new ApiHandler($keyConfig->api_key, $selfIps); - - return $app; - } - - /** - * Load and returns decoded Json from given file - * - * @access public - * @static - * @param string $filePath The file's full path - * @param bool $throwError Throw error on true or silent process. Default is true - * - * @return object|null - * @throws \Exception - * @throws \LogicException - */ - protected static function loadJsonFile(string $filePath, bool $throwError = true) - { - // check file exists - if (!file_exists($filePath) || !is_file($filePath)){ - if ($throwError) { - throw new \Exception('Config file not found'); - } - return null; - } - - // get and parse content - $content = utf8_encode(file_get_contents($filePath)); - $json = json_decode($content); - - // check for errors - if ($json == null && json_last_error() != JSON_ERROR_NONE && $throwError) { - throw new \LogicException(sprintf("Failed to parse config file Error: '%s'", json_last_error_msg())); - } - - return $json; - } - - /** - * Check for install - * - * @access protected - * @static - * - * @return bool - */ - protected static function checkForInstall() - { - if (file_exists(self::$keyPath)) { - return true; - } - - // not installed - self::printBanner(); - Console::log(' Your config key file was not found. Do you want to create it? ', 'white'); - $create = Console::ask(' Press Y/y to create a config key file: ', 'white'); - - if ($create == 'Y' || $create == 'y') { - $key = Console::ask(' - Please enter your api key: ', 'white'); - // $id = Console::ask(' - Please enter your user id: ', 'white'); - $data = json_encode(['api_key' => $key]); - $create = Console::ask(' A config file will be created in config/ directory. Press Y/y to continue: ', 'white'); - - if ($create == 'Y' || $create == 'y') { - - if (file_put_contents(self::$keyPath, $data, LOCK_EX) === false){ - self::error('An error occured during writing config file. Make sure to gave the appropriate permissions do the config directory.'); - return false; - } - - // successfull. print message and exit to prevent errors with no arguments - Console::log(); - Console::log(Console::text(' ✓ ', 'green') . Console::text('Your config file has been successfully created.', 'white')); - Console::log(' You can now use abuseipdb.', 'white'); - Console::log(); - exit(0); - } - } - - return false; - } - - /** - * Prints the help - * - * @access protected - * @static - * - * @return void - */ - protected static function printHelp() - { - Console::log(' ' . Console::text('SYNOPSIS:', 'white', 'underline')); - Console::log(' ' . Console::text(' abuseipdb -C ') . - Console::text('ip', 'yellow') . - Console::text(' [-d ') . - Console::text('days', 'yellow') . - Console::text('] [-v] [-l ') . - Console::text('limit', 'yellow') . - Console::text(']')); - - Console::log(' ' . Console::text(' abuseipdb -K ') . - Console::text('network', 'yellow') . - Console::text(' [-d ') . - Console::text('days', 'yellow') . - Console::text(']')); - - Console::log(' ' . Console::text(' abuseipdb -R ' . - Console::text('ip', 'yellow') . ' -c ' . - Console::text('categories', 'yellow') . ' -m ' . - Console::text('message', 'yellow'))); - - Console::log(' ' . Console::text(' abuseipdb -V ' . - Console::text('path', 'yellow'))); - - Console::log(' ' . Console::text(' abuseipdb -E ' . - Console::text('ip', 'yellow'))); - - Console::log(' ' . Console::text(' abuseipdb -B ') . - Console::text('[-l ') . - Console::text('limit', 'yellow') . - Console::text('] [-s ') . - Console::text('score', 'yellow') . - Console::text('] [-p ') . - Console::text('', 'yellow') . - Console::text(']')); - - Console::log(' ' . Console::text(' abuseipdb -L ')); - Console::log(' ' . Console::text(' abuseipdb -G ')); - Console::log(' ' . Console::text(' abuseipdb -h ')); - - Console::log(); - Console::log(' ' . Console::text('OPTIONS:', 'white', 'underline')); - Console::log(); - Console::log(Console::text(' -h, --help', 'white')); - Console::log(' Prints the current help. If given, all next arguments are ignored.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -G, --config', 'white')); - Console::log(' Prints the current config. If given, all next arguments are ignored.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -L, --list', 'white')); - Console::log(' Prints the list report categories. If given, all next arguments are ignored.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -C, --check ', 'white') . Console::text('ip', 'yellow', 'underline')); - Console::log(' Performs a check request for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -K, --check-block ', 'white') . Console::text('network', 'yellow', 'underline')); - Console::log(' Performs a check-block request for the given network. A valid subnet (v4 or v6) denoted with ', 'lightgray'); - Console::log(' CIDR notation is required.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -d, --days ', 'white') . Console::text('days', 'yellow', 'underline')); - Console::log(' For a check or check-block request, defines the maxAgeDays. Min is 1, max is 365, default is 30.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -R, --report ', 'white') . Console::text('ip', 'yellow', 'underline')); - Console::log(' Performs a report request for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -V, --bulk-report ', 'white') . Console::text('path', 'yellow', 'underline')); - Console::log(' Performs a bulk-report request sending a csv file. A valid file name or full path is required.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -E, --clear ', 'white')); - Console::log(' Remove own reports for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -c, --categories ', 'white') . Console::text('categories', 'yellow', 'underline')); - Console::log(' For a report request, defines the report category(ies). Categories must be separate by a comma.', 'lightgray'); - Console::log(' Some categories cannot be used alone. A category can be represented by its shortname or by its', 'lightgray'); - Console::log(Console::text(' id. Use ','lightgray') . Console::text('abuseipdb -L', 'white') . Console::text(' to print the categories list.','lightgray')); - Console::log(); - Console::log(Console::text(' -m, --message ', 'white') . Console::text('message', 'yellow', 'underline')); - Console::log(' For a report request, defines the message to send with report. Message is required for all', 'lightgray'); - Console::log(' report requests.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -B, --blacklist ', 'white')); - Console::log(' Performs a blacklist request. Default limit is 1000. This limit can ne changed with the', 'lightgray'); - Console::log(' ' . Console::text('--limit', 'white') . Console::text(' parameter. ', 'lightgray')); - Console::log(); - Console::log(Console::text(' -l, --limit ', 'white') . Console::text('limit', 'yellow', 'underline')); - Console::log(' For a blacklist request, defines the limit.', 'lightgray'); - Console::log(' For a check request with verbose flag, sets the max number of last reports displayed. Default is 10', 'lightgray'); - Console::log(' For a check-block request, sets the max number of IPs displayed. Default is 0 (no limit).', 'lightgray'); - Console::log(); - Console::log(Console::text(' -p, --plaintext ', 'white')); - Console::log(' For a blacklist request, output only ip list as plain text.', 'lightgray'); - Console::log(); - Console::log(Console::text(' -s, --score ', 'white')); - Console::log(' For a blacklist request, sets the confidence score minimum. The confidence minimum ', 'lightgray'); - Console::log(' must be between 25 and 100. This parameter is subscriber feature (not honored otherwise, allways 100).', 'lightgray'); - Console::log(); - Console::log(Console::text(' -v, --verbose ', 'white')); - Console::log(' For a check request, display additional fields like the x last reports. This increases ', 'lightgray'); - Console::log(Console::text(' request time and response size. Max number of last reports displayed can be changed with the ', 'lightgray')); - Console::log(' ' . Console::text('--limit', 'white') . Console::text(' parameter. ', 'lightgray')); - Console::log(); - } - - /** - * Prints the current config - * - * @access protected - * @static - * - * @return void - */ - protected static function printConfig() - { - // print current config and exit - $conf = self::$api->getConfig(); - - // banner - Console::log(); - Console::log(' ► Current configuration ', 'darkgray'); - Console::log(); - - // print config - Console::log(Console::text(' api_key:[', 'white') . Console::text($conf['apiKey'], 'green') . Console::text(']', 'white')); - Console::log(Console::text(' self_ips:', 'white')); - - foreach ($conf['selfIps'] as $ip) { - Console::log(Console::text(' [', 'white') . - Console::text($ip, 'green') . - Console::text(']', 'white') - ); - } - Console::log(); - self::printFooter(); - } - - /** - * Prints the report categories list - * - * @access protected - * @static - * - * @return void - */ - protected static function printCategories() - { - $categories = self::$api->getCategories(); - - // banner - Console::log(); - Console::log(' ► Report categories list ', 'darkgray'); - Console::log(); - - $rowHeaders = [ - Console::text('ShortName', 'darkgray') => 15, - Console::text('Id', 'darkgray') => 2, - Console::text('Full name', 'darkgray') => 18, - Console::text('Can be alone?', 'darkgray') => 15 - ]; - Console::$verticalSeparator = ' '; - Console::$verticalInnerSeparator = ' '; - Console::log(Console::tableRowSeparator($rowHeaders, 'darkgray')); - Console::log(Console::tableRow($rowHeaders)); - Console::log(Console::tableRowSeparator($rowHeaders), 'darkgray'); - - foreach ($categories as $cat) { - $id = Console::text($cat[1], 'white'); - $standalone = $cat[3] ? Console::text('✓', 'green') . Console::text(' true ', 'lightgray') : - Console::text('✗', 'red') . Console::text(' false', 'darkgray'); - $shortName = Console::text($cat[0], 'white'); - $fullName = Console::text($cat[2], 'lightgray'); - - Console::log( - Console::TableRowStart(). - Console::TableRowCell( $shortName , 15). - Console::TableRowCell( $id , 2, Console::ALIGN_CENTER). - Console::TableRowCell( $fullName , 18). - Console::TableRowCell( $standalone , 15, Console::ALIGN_CENTER) - ); - } - //Console::log(Console::tableRowSeparator($rowHeaders), 'darkgray'); - Console::log(); - self::printFooter(); - - } - - /** - * Perform a report request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function reportIP(array $arguments) - { - // make sure ip argument is given - // make sure categories argument is given - // make sure message argument is given - $ip = self::getArgumentValue($arguments,'R', 'report', 'No valid IP value given.'); - $cats = self::getArgumentValue($arguments,'c', 'categories', 'Report category was empty. At least on category is required for report requests.'); - $message = self::getArgumentValue($arguments,'m', 'message', 'Report message was empty. A message is required for report requests.'); - - // banner - Console::log(); - Console::log(Console::text(' ► Report IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - - // ---------------- - // Peforms request - // ---------------- - $timeStart = microtime(true); // request startime - $report = null; - - try { - $report = self::$api->report($ip, $cats, $message)->getObject(); - - } catch (\Exception $e) { - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - - // check for errors - if (self::printErrors($report)){ - self::printFooter(); - exit(1); - } - - // empty response ? - if (empty($report) || empty($report->data)){ - self::error('An unexpected error occurred.'); - self::printFooter(); - exit(1); - } - - // ---------------------------------------------- - // ✓ Done: print reported IP and confidence score - // ---------------------------------------------- - $score = empty($report->data->abuseConfidenceScore) ? 0 : $report->data->abuseConfidenceScore; - $scoreColor = self::getScoreColor($score); - Console::log( - Console::text(' ✓', 'green') . - Console::text(' IP: [', 'white') . - Console::text($ip, $scoreColor) . - Console::text('] successfully reported', 'white') - ); - Console::log(Console::text(' Confidence score: ', 'white') . self::getScoreBadge($score)); - - Console::log(); - self::printFooter($time); - } - - /** - * Perform a bulk-report request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function bulkReport(array $arguments) - { - $fileName = self::getArgumentValue($arguments,'V', 'bulk-report', 'No valid file name given.'); - - // banner - Console::log(); - Console::log(Console::text(' ► Bulk report for file: ', 'darkgray') . Console::text(escapeshellcmd($fileName), 'white')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - - // ---------------- - // Peforms request - // ---------------- - $timeStart = microtime(true); // request startime - $response = null; - - try { - $response = self::$api->bulkReport($fileName)->getObject(); - - } catch (\Exception $e) { - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - - // check for errors - if (self::printErrors($response)){ - self::printFooter(); - exit(1); - } - - // empty response ? - if (empty($response) || empty($response->data)){ - self::error('An unexpected error occurred.'); - self::printFooter(); - exit(1); - } - - // ---------------------------------------------- - // ✓ Done - // ---------------------------------------------- - Console::log( - Console::text(' Bulk report for file: [', 'white') . - Console::text($fileName, 'lightyellow') . - Console::text('] done!', 'white') - ); - - $nbErrorReports = isset($response->data->invalidReports) ? count($response->data->invalidReports) : 0; - $nbSavedReports = isset($response->data->savedReports) ? $response->data->savedReports : 0; - $savedColor = $nbSavedReports > 0 ? 'green' : 'red'; - $errorColor = $nbErrorReports > 0 ? 'red' : 'green'; - $savedIcon = $nbSavedReports > 0 ? '✓' : '✗'; - $errorIcon = $nbErrorReports > 0 ? '✗' : '✓'; - - Console::log(Console::text(' ' . $savedIcon, $savedColor) . self::printResult(' Saved reports: ', $nbSavedReports, $savedColor, '', false)); - Console::log(Console::text(' ' . $errorIcon, $errorColor) . self::printResult(' Invalid reports: ', $nbErrorReports, $errorColor, '', false)); - - if ($nbErrorReports > 0){ - $numberDiplayedReports = 0; - $defaultColor = 'lightyellow'; // reset color for last reports - - foreach ($response->data->invalidReports as $report){ - $input = $report->input ? escapeshellcmd($report->input) : ''; // in case on blank line, IP is null - $line = Console::text(' →', 'red'); - $line .= self::printResult(' Input: ', $input, $defaultColor, '', false); - Console::log($line); - self::printResult(' Error: ', $report->error, $defaultColor); - self::printResult(' Line number: ', $report->rowNumber, $defaultColor); - - // counter - $numberDiplayedReports++; - } - } - Console::log(); - self::printFooter($time); - } - - - /** - * Perform a clear-address request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function clearIP(array $arguments) - { - // make sure ip argument is given - $ip = self::getArgumentValue($arguments,'E', 'clear', 'No valid IP value given.'); - - // banner - Console::log(); - Console::log(Console::text(' ► Clear reports for IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - - // ---------------- - // Peforms request - // ---------------- - $timeStart = microtime(true); // request startime - $response = null; - - try { - $response = self::$api->clearAddress($ip)->getObject(); - - - } catch (\Exception $e) { - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - // done, we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - - // check for errors - if (self::printErrors($response)){ - self::printFooter($time); - exit(1); - } - - // empty response ? - if (empty($response) || empty($response->data)){ - self::error('An unexpected error occurred.'); - self::printFooter($time); - exit(1); - } - - // ---------------------------------------------- - // ✓ Done - // ---------------------------------------------- - Console::log( - Console::text(' ✓', 'green') . - Console::text(' Successfull clear request for IP: [', 'white') . - Console::text($ip, 'lightyellow') . - Console::text(']', 'white') - ); - - $line = Console::text(' Deleted reports: ', 'white'); - $line .= Console::text($response->data->numReportsDeleted, 'lightyellow'); - Console::log($line); - - - Console::log(); - self::printFooter($time); - } - - /** - * Perform a blacklist request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function getBlacklist(array $arguments) - { - $plainText = self::inArguments($arguments,'p','plaintext'); - $limit = self::getNumericParameter($arguments,'l', 'limit', 1000); - $scoreMin = self::getNumericParameter($arguments,'s', 'score', 100); - - if (!$plainText){ - Console::log(); - Console::log(Console::text(' ► Get Blacklist ', 'darkgray')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - } - - // do request - $timeStart = microtime(true); // request startime - - try { - $response = self::$api->blacklist($limit, $plainText, $scoreMin); // perform request - } catch (\Exception $e) { - if (!$plainText){ - // we clear previous the message. Makes sure we clear the whole line with long string - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - } - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - if (!$plainText){ - // done, clean previous message - Console::reLog(' '); - } - - // response could be json on error, while plaintext flag is set - $decodedResponse = $response->getObject(); - - if (self::printErrors($decodedResponse)){ - self::printFooter($time); - exit(1); - } - - if (!$plainText && empty($decodedResponse->data)){ - self::error('An unexpected error occurred.'); - self::printFooter($time); - exit(1); - } - - if ($plainText){ - // echo response "as is" - Console::log($response->getPlaintext()); - - } else { - - self::printResult(' List generated at: ', self::getDate($decodedResponse->meta->generatedAt), 'lightyellow', ''); - Console::log(); - - foreach ($decodedResponse->data as $report){ - $score = empty($report->abuseConfidenceScore) ? 0 : $report->abuseConfidenceScore; - $defaultColor = self::getScoreColor($score); - - $line = Console::text(' →', $defaultColor); - $line .= self::printResult(' IP: ', $report->ipAddress, $defaultColor, '', false); - $line .= self::printResult(' | Last reported at: ', self::getDate($report->lastReportedAt), $defaultColor, '', false); - $line .= Console::text(' | Confidence score: ', 'white'); - $line .= self::getScoreBadge($score); - Console::log($line); - } - - // footer - Console::log(); - self::printFooter($time); - } - } - - /** - * Perform a check-block request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function checkBlock($arguments) - { - $network = self::getArgumentValue($arguments,'K', 'check-block', 'No valid network value given.'); - $maxAge = self::getNumericParameter($arguments, 'd', 'days', 30); - $limit = self::getNumericParameter($arguments,'l', 'limit', 0); // 0 mean no limit - - // banner - Console::log(); - Console::log(Console::text(' ► Check network: ', 'darkgray') . Console::text(escapeshellcmd($network), 'white') . Console::text('', 'darkgray')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - - // do request - $timeStart = microtime(true); // request startime - - try { - $check = self::$api->checkBlock($network, $maxAge)->getObject(); // perform check - - } catch (\Exception $e) { - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - // done, clean previous message - Console::reLog(' '); - - if (self::printErrors($check)){ - self::printFooter($time); - exit(1); - } - - if (empty($check->data)){ - self::error('An unexpected error occurred.'); - self::printFooter($time); - exit(1); - } - - self::printResult(Console::pad(' Network Address:', 23), $check->data->networkAddress, 'lightyellow'); - self::printResult(Console::pad(' Netmask:', 23), $check->data->netmask, 'lightyellow'); - self::printResult(Console::pad(' Min Address:', 23), $check->data->minAddress, 'lightyellow'); - self::printResult(Console::pad(' Max Address:', 23), $check->data->maxAddress, 'lightyellow'); - self::printResult(Console::pad(' Possible Hosts:', 23), $check->data->numPossibleHosts, 'lightyellow'); - self::printResult(Console::pad(' Address SpaceDesc:', 23), $check->data->addressSpaceDesc, 'lightyellow'); - - // print reported addresses - $nbReports = isset($check->data->reportedAddress) ? count($check->data->reportedAddress) : 0; - - if ($nbReports > 0){ - self::printResult(Console::pad(' Reported addresses:', 23), $nbReports, 'lightyellow'); - $numberDiplayedReports = 0; - $defaultColor = 'lightyellow'; // reset color for last reports - - foreach ($check->data->reportedAddress as $report){ - $score = empty($report->abuseConfidenceScore) ? 0 : $report->abuseConfidenceScore; - $defaultColor = self::getScoreColor($score); // color based on score - - $line = Console::text(' →', $defaultColor); - $line .= self::printResult(' IP: ', $report->ipAddress, $defaultColor, '', false); - $line .= self::printResult(' Country: ', $report->countryCode , $defaultColor, '', false); - $line .= Console::text(' | Confidence score: ', 'white'); - $line .= self::getScoreBadge($score); - $line .= self::printResult(' Total reports: ', $report->numReports, $defaultColor, '', false); - $line .= self::printResult(' Last reported at: ', self::getDate($report->mostRecentReport), $defaultColor, '', false); - Console::log($line); - - // counter - $numberDiplayedReports++; - - if ($numberDiplayedReports === $limit || $numberDiplayedReports === $nbReports) { - $line = Console::text(' (', 'white'); - $line .= Console::text($numberDiplayedReports, 'lightyellow'); - $line .= Console::text('/', 'white'); - $line .= Console::text($nbReports, 'lightyellow'); - $line .= Console::text($numberDiplayedReports > 1 ? ' IPs displayed)': ' IP displayed)', 'white'); - Console::log($line); - break; - } - } - - } else { - // no reports - $day = $maxAge > 1 ? 'in last '. $maxAge . ' days': ' today'; - Console::log( Console::text(' ✓', 'green') . Console::text(' No IP reported ' . $day)); - } - - // footer - Console::log(); - self::printFooter($time); - } - - /** - * Perform a check request - * - * @access protected - * @static - * @param array $arguments - * - * @return void - */ - protected static function checkIP($arguments) - { - $ip = self::getArgumentValue($arguments,'C', 'check', 'No valid IP value given.'); - $verbose = self::inArguments($arguments,'v', 'verbose'); - $maxAge = self::getNumericParameter($arguments, 'd', 'days', 30); - $maxReportsNumber = self::getNumericParameter($arguments,'l', 'limit', 10); - - // banner - Console::log(); - Console::log(Console::text(' ► Check IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white') . Console::text('', 'darkgray')); - Console::log(); - - // temporary message - Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); - - // do request - $timeStart = microtime(true); // request startime - - try { - $check = self::$api->check($ip, $maxAge, $verbose)->getObject(); // perform check - - - } catch (\Exception $e) { - Console::reLog(' '); - self::error($e->getMessage()); - self::printFooter(); - exit(1); - } - - $timeEnd = microtime(true); // request end time - $time = $timeEnd - $timeStart; // request time - - // done, clean previous message - Console::reLog(' '); - - if (self::printErrors($check)){ - self::printFooter($time); - exit(1); - } - - if (empty($check->data)){ - self::error('An unexpected error occurred.'); - self::printFooter($time); - exit(1); - } - - // score and data color (depending of abuseConfidenceScore) - $score = empty($check->data->abuseConfidenceScore) ? 0 : $check->data->abuseConfidenceScore; - $defaultColor = self::getScoreColor($score); - $line = Console::text(Console::pad(' Confidence score:', 23), 'white'); - $line .= self::getScoreBadge($score); - Console::log($line); - -// self::printResult(' isPublic', $check->data->isPublic, $defaultColor); -// self::printResult(' ipVersion', $check->data->ipVersion, $defaultColor); - $line = self::printResult(Console::pad(' Whitelisted:', 23), $check->data->isWhitelisted ? 'true': 'false', $defaultColor, '', false); - $line .= $check->data->isWhitelisted ? Console::text(' ★', 'green') : ''; - Console::log($line); - - self::printResult(Console::pad(' Country code:', 23), $check->data->countryCode, $defaultColor); - - if (!empty($check->data->countryName)){ - self::printResult(Console::pad(' Country name:', 23), $check->data->countryName, $defaultColor); - } - - self::printResult(Console::pad(' ISP:', 23), $check->data->isp, $defaultColor); - - if ($check->data->usageType){ - $line = self::printResult(Console::pad(' Usage type:', 23), $check->data->usageType, $defaultColor, '', false); - $line .= $check->data->usageType === 'Reserved' ? Console::text(' ◆', 'green') : ''; - Console::log($line); - } - - $hostames = implode(', ', array_filter($check->data->hostnames)) ?? null; - if (!empty($hostames)){ - self::printResult(Console::pad(' Hostname(s):', 23), $hostames, $defaultColor); - } - - self::printResult(Console::pad(' Domain:', 23), $check->data->domain, $defaultColor); - - $nbReport = $check->data->totalReports && is_numeric($check->data->totalReports) ? intval($check->data->totalReports) : 0; - if ($nbReport > 0 ){ - $line = self::printResult(Console::pad(' Total reports:', 23), $nbReport, $defaultColor, '', false); - $line .= self::printResult(' from ', $check->data->numDistinctUsers, $defaultColor, '', false); - $line .= Console::text($nbReport > 0 ? ' distinct users': ' user', 'white'); - Console::log($line); - - } else { - // no reports - $day = $maxAge > 1 ? 'in last '. $maxAge . ' days': ' today'; - Console::log( Console::text(' ✓', 'green') . Console::text(' Not reported ' . $day)); - } - - if (!empty($check->data->lastReportedAt)){ - self::printResult(Console::pad(' Last reported at:', 23), self::getDate($check->data->lastReportedAt), $defaultColor); - } - - // print last reports - if ($verbose){ - $nbLastReports = isset($check->data->reports) ? count($check->data->reports) : 0; - - if ($nbLastReports > 0){ - Console::log(' Last reports:', 'white'); - $numberDiplayedReports = 0; - $defaultColor = 'lightyellow'; // reset color for last reports - - foreach ($check->data->reports as $lastReport){ - $categories = []; - foreach (array_filter($lastReport->categories) as $catId){ - $cat = self::$api->getCategoryNamebyId($catId)[0]; - if ($cat !== false) { - $categories[] = $cat; - } - } - - $line = Console::text(' →', $defaultColor); - $line .= self::printResult(' reported at: ', self::getDate($lastReport->reportedAt), $defaultColor, '', false); - // $line .= self::printResult(' by user: ', $lastReport->reporterId, $defaultColor, '', false); - if (isset($lastReport->reporterCountryCode) && isset($lastReport->reporterCountryName)){ - $line .= Console::text(' from: ', 'white'); - $line .= self::printResult('', $lastReport->reporterCountryCode, $defaultColor, '', false); - $line .= Console::text(' - ', 'white'); - $line .= self::printResult('', $lastReport->reporterCountryName, $defaultColor, '', false); - } - $line .= Console::text(' with categor' . (count($categories) > 1 ? "ies: " : "y: "), 'white'); - foreach ($categories as $key => $cat) { - $line .= Console::text($key==0 ? '' : ',' , 'white') . Console::text($cat, $defaultColor); - } - Console::log($line); - - // counter - $numberDiplayedReports++; - if ($numberDiplayedReports === $maxReportsNumber || $numberDiplayedReports === $nbLastReports) { - $line = Console::text(' (', 'white'); - $line .= Console::text($numberDiplayedReports, $defaultColor); - $line .= Console::text('/', 'white'); - $line .= Console::text($nbLastReports, $defaultColor); - $line .= Console::text($numberDiplayedReports > 1 ? ' reports displayed)': ' report displayed)', 'white'); - Console::log($line); - break; - } - } - } - } - - // footer - Console::log(); - self::printFooter($time); - } - - /** - * Get numeric parameter and exit on error - * - * @access protected - * @static - * @param array $arguments - * @param string $shortArg The short argument name - * @param string $longArg The long argument name - * @param int $defaultValue - * - * @return int - */ - protected static function getNumericParameter(array $arguments, string $shortArg, string $longArg, int $defaultValue) - { - // check if max age is given - if (self::inArguments($arguments,$shortArg, $longArg)){ - $val = self::getArgumentValue($arguments,$shortArg, $longArg); - - if (!is_numeric($val)){ - self::error("$longArg must be a numeric value."); - self::printFooter(); - exit(1); - } - return intval($val); - } - return $defaultValue; - } - - /** - * Check and print errors in API response - * - * @access protected - * @static - * @param string $text - * @param int $score - * @param string $textColor - * - * @return bool - */ - protected static function printErrors($response) - { - if (isset($response) && isset($response->errors)){ - // top error badge - Console::log(' ' . Console::text(' ERROR ','white', 'red')); - - // errors is an array, could have more than one error.. - foreach ($response->errors as $err){ - Console::log(Console::text(' ✗', 'red') . Console::text(' status: [', 'white') . Console::text($err->status, 'red') . Console::text(']', 'white')); - - if (!empty($err->source) && !empty($err->source->parameter)){ - Console::log(Console::text(' parameter: [', 'white') . Console::text($err->source->parameter, 'red') . Console::text(']', 'white')); - } - Console::log(Console::text(' detail: [', 'white') . Console::text($err->detail, 'red') . Console::text(']', 'white')); - - // separate errors - if (count($response->errors) > 1){ - Console::log(' ---'); - } - - } - Console::log(); - return true; - } - - return false; - } - - /** - * Prints score badge - * - * @access protected - * @static - * @param string $text - * @param int $score - * @param string $textColor - * - * @return string - */ - protected static function getScoreBadge(int $score, string $padding = ' ') - { - $scoreforegroundColor = 'white'; - $scoreBackgroundColor = 'green'; - - if (intval($score) > 0 ){ - $scoreforegroundColor = 'black'; - $scoreBackgroundColor = 'yellow'; - } - if (intval($score) > 50 ){ - $scoreforegroundColor = 'white'; - $scoreBackgroundColor = 'red'; - } - - $badge = str_pad($score, 3, ' ',STR_PAD_LEFT); - return Console::text($padding.$badge.$padding, $scoreforegroundColor, $scoreBackgroundColor); - } - - /** - * Prints/gets a result value - * - * @access protected - * @static - * - * @return string - */ - protected static function printResult($text, $value, string $foregroundColor = 'lightred', string $backgroundColor = '', bool $print = true) - { - // do not print null/blank values - if (isset($value)){ - $line = Console::text($text, 'white') . Console::text($value, $foregroundColor, $backgroundColor); - if ($print){ - Console::log($line); - } - return $line; - } - - return ''; - } - - /** - * Print app banner - * - * @access protected - * @static - * - * @return void - */ - protected static function printBanner() - { - Console::log(); - Console::log( Console::text(' Kristuff\AbuseIPDB ', 'darkgray') . Console::text(' ' . self::$version . ' ', 'white', 'blue')); - Console::log(Console::text(' Made with ', 'darkgray') . Console::text('♥', 'red') . Console::text(' in France', 'darkgray')); - Console::log(' © 2020-2021 Kristuff', 'darkgray'); - Console::log(); - } - - - /** - * Print action banner - * - * @access protected - * @static - * - * @return void - */ - protected static function printActionBanner(string $text, int $lenght) - { - Console::log(Console::pad(' ', $lenght), 'lightgray'); - Console::log(' ' . $text); - Console::log(Console::pad(' ', $lenght), 'lightgray'); - } - - /** - * Print footer banner - * - * @access protected - * @static - * - * @return void - */ - protected static function printFooter(string $requestTime = '') - { - - - if (!empty($requestTime)){ - $date_utc = new \DateTime("now", new \DateTimeZone("UTC")); - Console::log(Console::text(' Request time: ', 'darkgray') . - Console::text($requestTime . 's', 'lightgray'). - Console::text(' | UTC time: ', 'darkgray') . - Console::text($date_utc->format('Y-m-d H:i:s'), 'lightgray') - ); - - } - - Console::log(Console::text(' ------------------------------------------------------------------------------------------------------', 'darkgray')); - Console::log( - Console::text(' Kristuff\AbuseIPDB ', 'darkgray') . - Console::text(self::$version, 'lightgray') . - Console::text(' | Made with ', 'darkgray') . - Console::text('♥', 'red') . - Console::text(' in France | © 2020-2021 Kristuff (https://github.com/kristuff)', 'darkgray') - ); - Console::log(); - } - - /** - * Print an error - * - * @access protected - * @static - * @param string $error The error message - * - * @return void - */ - protected static function error($error) - { - // ✗ - Console::log(' ' . Console::text(' ERROR ','white', 'red')); - Console::log( - Console::text(' ✗ ', 'red') . - Console::text('', 'white') . - Console::text($error, 'red') . - Console::text('', 'white') - ); - Console::log(); - } - - /** - * helper function to get formatted date - * - * @access private - * @static - * @param string $date The UTC date - * - * @return string Formated time - */ - private static function getDate($date) - { - //2020-05-22T17:06:35+00:00 - return \DateTime::createFromFormat('Y-m-d\TH:i:s+', $date) - ->format('Y-m-d H:i:s'); - } - - /** - * helper function to check if a argument is given - * - * @access protected - * @static - * @param array $arguments The list of arguments - * @param array $shortArg The short argument name - * @param array $longArg The long argument name - * - * @return bool True if the short or long argument exist in the arguments array, otherwise false - */ - protected static function inArguments($arguments, $shortArg, $longArg) - { - return array_key_exists($shortArg, $arguments) || array_key_exists($longArg, $arguments); - } - - /** - * helper function to get the value of an argument - * - * - * @access protected - * @static - * @param array $arguments The list of arguments - * @param string $shortArg The short argument name - * @param string $longArg The long argument name - * @param string $error The error message to display when value is required but missing - * @param bool $exitOnError Exit with code 1 on error. Default is true - * - * @return string|null - * - */ - protected static function getArgumentValue(array $arguments, string $shortArg, string $longArg, string $error = '', bool $exitOnError = true) - { - $val = array_key_exists($shortArg, $arguments) ? $arguments[$shortArg] : - (array_key_exists($longArg, $arguments) ? $arguments[$longArg] : null); - - if (empty($val) && !empty($error) ){ - self::error($error); - if ($exitOnError) { - exit(1); - } - } - return $val; - - } - - /** - * helper function to get the color corresponding to given score - * - * @access protected - * @static - * @param mixed $score - * - * @return string - * - */ - protected static function getScoreColor($score) - { - $score = intval($score); - return $score > 50 ? 'lightred' : ($score > 0 ? 'yellow' : 'green') ; - } - -} ?> \ No newline at end of file diff --git a/composer.json b/composer.json index dc1f199..2b00a57 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "kristuff/abuseipdb-cli", "description": "A CLI tool to check/report IP addresses with AbuseIPDB API V2", - "type": "project", + "type": "library", "license": "MIT", "authors": [ { @@ -12,11 +12,14 @@ "require": { "php": ">=7.1", "kristuff/mishell": "^1.4-stable", - "kristuff/abuseipdb": "0.9.8" + "kristuff/abuseipdb": "0.9.9" }, + "bin": [ + "bin/abuseipdb" + ], "autoload": { "psr-4": { - "Kristuff\\AbuseIPDB\\": "bin/" + "Kristuff\\AbuseIPDB\\": "src/" } } } \ No newline at end of file diff --git a/config/self_ips.sample.json b/config/self_ips.sample.json index aee5a32..3210647 100644 --- a/config/self_ips.sample.json +++ b/config/self_ips.sample.json @@ -1,6 +1,6 @@ { "self_ips": [ - "xx9999.ip-256-256-256.xx", + "xx9999.ip-256-256-256.xx", "256.256.256.256", "subdomain.example.com", "example.com", diff --git a/src/AbuseIPDBClient.php b/src/AbuseIPDBClient.php new file mode 100644 index 0000000..8acf310 --- /dev/null +++ b/src/AbuseIPDBClient.php @@ -0,0 +1,895 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @version 0.9.8 + * @copyright 2020-2021 Kristuff + */ +namespace Kristuff\AbuseIPDB; + +use Kristuff\AbuseIPDB\SilentApiHandler; +use Kristuff\Mishell\Console; + +/** + * Class AbuseIPDB + * + * The main cli program + */ +class AbuseIPDBClient extends ShellUtils +{ + + /** + * @var string + */ + const SHORT_ARGUMENTS = "GLBK:C:d:R:c:m:l:pE:V:hvs:"; + + /** + * @var string + */ + const LONG_ARGUMENTS = ['config', 'list', 'blacklist', 'check:', 'check-block:', 'days:', 'report:', 'categories:', 'message:', 'limit:', 'plaintext', 'clear:','bulk-report:', 'help', 'verbose', 'score:']; + + /** + * @var string $version + */ + const VERSION = 'v0.9.8'; + + /** + * @var SafeApiHandler $api + */ + private static $api = null; + + /** + * @var string $keyPath + */ + private static $keyPath = __DIR__ .'/../config/key.json'; + + /** + * The entry point of our app + * + * @access public + * @static + * @param array $arguments + * + * @return void + */ + public static function start($arguments) + { + // get key path from current script location (supposed in a bin folder) + self::$keyPath = dirname(get_included_files()[0]) . '/../config/key.json'; + + // check for install + self::validate( self::checkForInstall(), 'Key file missing.'); + + // Create a new instance of \ApiHandler with the given config file + try { + self::$api = self::fromConfigFile(self::$keyPath); + } catch (\Exception $e) { + self::error($e->getMessage()); + self::printFooter(); + exit(1); + } + + // required at least one valid argument + self::validate( !empty($arguments), 'No valid arguments given. Run abuseipdb --help to get help.'); + + // prints help ? + if (self::inArguments($arguments, 'h', 'help')){ + self::printBanner(); + self::printHelp(); + exit(0); + } + + // prints config ? + if (self::inArguments($arguments, 'G', 'config')){ + self::printConfig(); + exit(0); + } + + // prints catgeories ? + if (self::inArguments($arguments, 'L', 'list')){ + self::printCategories(); + exit(0); + } + + // check request ? + if (self::inArguments($arguments, 'C', 'check')){ + self::checkIP($arguments); + exit(0); + } + + // check-block request ? + if (self::inArguments($arguments, 'K', 'check-block')){ + self::checkBlock($arguments); + exit(0); + } + + // report request ? + if (self::inArguments($arguments, 'R', 'report')){ + self::reportIP($arguments); + exit(0); + } + + // report request ? + if (self::inArguments($arguments, 'V', 'bulk-report')){ + self::bulkReport($arguments); + exit(0); + } + + // report request ? + if (self::inArguments($arguments, 'B', 'blacklist')){ + self::getBlacklist($arguments); + exit(0); + } + + // report request ? + if (self::inArguments($arguments, 'E', 'clear')){ + self::clearIP($arguments); + exit(0); + } + + // no valid arguments given, close program + Console::log(); + self::error('invalid arguments. Run abuseipdb --help to get help.'); + self::printFooter(); + exit(1); + } + + /** + * Get a new instance of ApiHandler with config stored in a Json file + * + * @access public + * @static + * @param string $configPath The configuration file path + * + * @return \Kristuff\AbuseIPDB\ApiHandler + * @throws \InvalidArgumentException If the given file does not exist + * @throws \Kristuff\AbuseIPDB\InvalidPermissionException If the given file is not readable + */ + public static function fromConfigFile(string $configPath) + { + // check file exists + if (!file_exists($configPath) || !is_file($configPath)){ + throw new \InvalidArgumentException('The file [' . $configPath . '] does not exist.'); + } + + // check file is readable + if (!is_readable($configPath)){ + throw new InvalidPermissionException('The file [' . $configPath . '] is not readable.'); + } + + $keyConfig = self::loadJsonFile($configPath); + $selfIps = []; + + // Look for other optional config files in the same directory + $selfIpsConfigPath = pathinfo($configPath, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . 'self_ips.json'; + if (file_exists($selfIpsConfigPath)){ + $selfIps = self::loadJsonFile($selfIpsConfigPath)->self_ips; + } + + $app = new SilentApiHandler($keyConfig->api_key, $selfIps); + + return $app; + } + + /** + * Load and returns decoded Json from given file + * + * @access public + * @static + * @param string $filePath The file's full path + * @param bool $throwError Throw error on true or silent process. Default is true + * + * @return object|null + * @throws \Exception + * @throws \LogicException + */ + protected static function loadJsonFile(string $filePath, bool $throwError = true) + { + // check file exists + if (!file_exists($filePath) || !is_file($filePath)){ + if ($throwError) { + throw new \Exception('Config file not found'); + } + return null; + } + + // get and parse content + $content = utf8_encode(file_get_contents($filePath)); + $json = json_decode($content); + + // check for errors + if ($json == null && json_last_error() != JSON_ERROR_NONE && $throwError) { + throw new \LogicException(sprintf("Failed to parse config file Error: '%s'", json_last_error_msg())); + } + + return $json; + } + + /** + * Check for install + * + * @access protected + * @static + * + * @return bool + */ + protected static function checkForInstall() + { + if (file_exists(self::$keyPath)) { + return true; + } + + // not installed + self::printBanner(); + Console::log(' Your config key file was not found. Do you want to create it? ', 'white'); + $create = Console::ask(' Press Y/y to create a config key file: ', 'white'); + + if ($create == 'Y' || $create == 'y') { + $key = Console::ask(' - Please enter your api key: ', 'white'); + $create = Console::ask(' A config file will be created in config/ directory. Press Y/y to continue: ', 'white'); + + if ($create == 'Y' || $create == 'y') { + $data = json_encode(['api_key' => $key]); + + if (file_put_contents(self::$keyPath, $data, LOCK_EX) === false){ + self::error('An error occured during writing config file. Make sure to give the appropriate permissions do the config directory.'); + return false; + } + + // successfull. print message and exit to prevent errors with no arguments + Console::log(); + Console::log(Console::text(' ✓ ', 'green') . Console::text('Your config file has been successfully created.', 'white')); + Console::log(' You can now use abuseipdb.', 'white'); + Console::log(); + exit(0); + } + } + // no key file, not created + return false; + } + + /** + * Prints the help + * + * @access protected + * @static + * + * @return void + */ + protected static function printHelp() + { + Console::log(' ' . Console::text('SYNOPSIS:', 'white', 'underline')); + Console::log(' ' . Console::text(' abuseipdb -C ') . + Console::text('ip', 'yellow') . + Console::text(' [-d ') . + Console::text('days', 'yellow') . + Console::text('] [-v] [-l ') . + Console::text('limit', 'yellow') . + Console::text(']')); + + Console::log(' ' . Console::text(' abuseipdb -K ') . + Console::text('network', 'yellow') . + Console::text(' [-d ') . + Console::text('days', 'yellow') . + Console::text(']')); + + Console::log(' ' . Console::text(' abuseipdb -R ' . + Console::text('ip', 'yellow') . ' -c ' . + Console::text('categories', 'yellow') . ' -m ' . + Console::text('message', 'yellow'))); + + Console::log(' ' . Console::text(' abuseipdb -V ' . + Console::text('path', 'yellow'))); + + Console::log(' ' . Console::text(' abuseipdb -E ' . + Console::text('ip', 'yellow'))); + + Console::log(' ' . Console::text(' abuseipdb -B ') . + Console::text('[-l ') . + Console::text('limit', 'yellow') . + Console::text('] [-s ') . + Console::text('score', 'yellow') . + Console::text('] [-p ') . + Console::text('', 'yellow') . + Console::text(']')); + + Console::log(' ' . Console::text(' abuseipdb -L ')); + Console::log(' ' . Console::text(' abuseipdb -G ')); + Console::log(' ' . Console::text(' abuseipdb -h ')); + + Console::log(); + Console::log(' ' . Console::text('OPTIONS:', 'white', 'underline')); + Console::log(); + Console::log(Console::text(' -h, --help', 'white')); + Console::log(' Prints the current help. If given, all next arguments are ignored.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -G, --config', 'white')); + Console::log(' Prints the current config. If given, all next arguments are ignored.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -L, --list', 'white')); + Console::log(' Prints the list report categories. If given, all next arguments are ignored.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -C, --check ', 'white') . Console::text('ip', 'yellow', 'underline')); + Console::log(' Performs a check request for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -K, --check-block ', 'white') . Console::text('network', 'yellow', 'underline')); + Console::log(' Performs a check-block request for the given network. A valid subnet (v4 or v6) denoted with ', 'lightgray'); + Console::log(' CIDR notation is required.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -d, --days ', 'white') . Console::text('days', 'yellow', 'underline')); + Console::log(' For a check or check-block request, defines the maxAgeDays. Min is 1, max is 365, default is 30.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -R, --report ', 'white') . Console::text('ip', 'yellow', 'underline')); + Console::log(' Performs a report request for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -V, --bulk-report ', 'white') . Console::text('path', 'yellow', 'underline')); + Console::log(' Performs a bulk-report request sending a csv file. A valid file name or full path is required.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -E, --clear ', 'white')); + Console::log(' Remove own reports for the given IP address. A valid IPv4 or IPv6 address is required.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -c, --categories ', 'white') . Console::text('categories', 'yellow', 'underline')); + Console::log(' For a report request, defines the report category(ies). Categories must be separate by a comma.', 'lightgray'); + Console::log(' Some categories cannot be used alone. A category can be represented by its shortname or by its', 'lightgray'); + Console::log(Console::text(' id. Use ','lightgray') . Console::text('abuseipdb -L', 'white') . Console::text(' to print the categories list.','lightgray')); + Console::log(); + Console::log(Console::text(' -m, --message ', 'white') . Console::text('message', 'yellow', 'underline')); + Console::log(' For a report request, defines the message to send with report. Message is required for all', 'lightgray'); + Console::log(' report requests.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -B, --blacklist ', 'white')); + Console::log(' Performs a blacklist request. Default limit is 1000. This limit can ne changed with the', 'lightgray'); + Console::log(' ' . Console::text('--limit', 'white') . Console::text(' parameter. ', 'lightgray')); + Console::log(); + Console::log(Console::text(' -l, --limit ', 'white') . Console::text('limit', 'yellow', 'underline')); + Console::log(' For a blacklist request, defines the limit.', 'lightgray'); + Console::log(' For a check request with verbose flag, sets the max number of last reports displayed. Default is 10', 'lightgray'); + Console::log(' For a check-block request, sets the max number of IPs displayed. Default is 0 (no limit).', 'lightgray'); + Console::log(); + Console::log(Console::text(' -p, --plaintext ', 'white')); + Console::log(' For a blacklist request, output only ip list as plain text.', 'lightgray'); + Console::log(); + Console::log(Console::text(' -s, --score ', 'white')); + Console::log(' For a blacklist request, sets the confidence score minimum. The confidence minimum ', 'lightgray'); + Console::log(' must be between 25 and 100. This parameter is subscriber feature (not honored otherwise, allways 100).', 'lightgray'); + Console::log(); + Console::log(Console::text(' -v, --verbose ', 'white')); + Console::log(' For a check request, display additional fields like the x last reports. This increases ', 'lightgray'); + Console::log(Console::text(' request time and response size. Max number of last reports displayed can be changed with the ', 'lightgray')); + Console::log(' ' . Console::text('--limit', 'white') . Console::text(' parameter. ', 'lightgray')); + Console::log(); + } + + /** + * Prints the current config + * + * @access protected + * @static + * + * @return void + */ + protected static function printConfig() + { + $conf = self::$api->getConfig(); + + self::printTitle(Console::text(' ► Current configuration ', 'darkgray')); + + Console::log(Console::text(' api_key:[', 'white') . Console::text($conf['apiKey'], 'green') . Console::text(']', 'white')); + Console::log(Console::text(' self_ips:', 'white')); + + foreach ($conf['selfIps'] as $ip) { + Console::log(Console::text(' [', 'white') . Console::text($ip, 'green') . Console::text(']', 'white')); + } + + Console::log(); + self::printFooter(); + } + + /** + * Prints the report categories list + * + * @access protected + * @static + * + * @return void + */ + protected static function printCategories() + { + self::printTitle(Console::text(' ► Report categories list ', 'darkgray')); + + $categories = self::$api->getCategories(); + $rowHeaders = [ + Console::text('ShortName', 'darkgray') => 15, + Console::text('Id', 'darkgray') => 2, + Console::text('Full name', 'darkgray') => 18, + Console::text('Can be alone?', 'darkgray') => 15 + ]; + Console::$verticalSeparator = ' '; + Console::$verticalInnerSeparator = ' '; + Console::log(Console::tableRowSeparator($rowHeaders, 'darkgray')); + Console::log(Console::tableRow($rowHeaders)); + Console::log(Console::tableRowSeparator($rowHeaders), 'darkgray'); + + foreach ($categories as $cat) { + $id = Console::text($cat[1], 'white'); + $standalone = $cat[3] ? Console::text('✓', 'green') . Console::text(' true ', 'lightgray') : + Console::text('✗', 'red') . Console::text(' false', 'darkgray'); + $shortName = Console::text($cat[0], 'white'); + $fullName = Console::text($cat[2], 'lightgray'); + + Console::log( + Console::TableRowStart(). + Console::TableRowCell( $shortName , 15). + Console::TableRowCell( $id , 2, Console::ALIGN_CENTER). + Console::TableRowCell( $fullName , 18). + Console::TableRowCell( $standalone , 15, Console::ALIGN_CENTER) + ); + } + //Console::log(Console::tableRowSeparator($rowHeaders), 'darkgray'); + Console::log(); + self::printFooter(); + } + + /** + * Perform a report request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function reportIP(array $arguments) + { + $ip = self::getArgumentValue($arguments,'R', 'report'); + $cats = self::getArgumentValue($arguments,'c', 'categories'); + $message = self::getArgumentValue($arguments,'m', 'message'); + + self::printTitle(Console::text(' ► Report IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white')); + self::printTempMessage(); + + // Peforms request + $timeStart = microtime(true); + $report = self::$api->report($ip, $cats, $message)->getObject(); + $timeEnd = microtime(true); + $time = $timeEnd - $timeStart; // request time + self::clearTempMessage(); + + // check for errors / empty response + if (self::printErrors($report)){ + self::printFooter(); + exit(1); + } + + // ✓ Done: print reported IP and confidence score + $score = empty($report->data->abuseConfidenceScore) ? 0 : $report->data->abuseConfidenceScore; + $scoreColor = self::getScoreColor($score); + Console::log( + Console::text(' ✓', 'green') . + Console::text(' IP: [', 'white') . + Console::text($ip, $scoreColor) . + Console::text('] successfully reported', 'white') + ); + Console::log(Console::text(' Confidence score: ', 'white') . self::getScoreBadge($score)); + + Console::log(); + self::printFooter($time); + } + + /** + * Perform a bulk-report request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function bulkReport(array $arguments) + { + $fileName = self::getArgumentValue($arguments,'V', 'bulk-report'); + + self::printTitle(Console::text(' ► Bulk report for file: ', 'darkgray') . Console::text(escapeshellcmd($fileName), 'white')); + self::printTempMessage(); + + // Peforms request + $timeStart = microtime(true); + $response = self::$api->bulkReport($fileName)->getObject(); + $timeEnd = microtime(true); + $time = $timeEnd - $timeStart; // request time + self::clearTempMessage(); + + // check for errors / empty response + if (self::printErrors($response)){ + self::printFooter(); + exit(1); + } + + // ✓ Done + Console::log( + Console::text(' Bulk report for file: [', 'white') . + Console::text($fileName, 'lightyellow') . + Console::text('] done!', 'white') + ); + + $nbErrorReports = isset($response->data->invalidReports) ? count($response->data->invalidReports) : 0; + $nbSavedReports = isset($response->data->savedReports) ? $response->data->savedReports : 0; + $savedColor = $nbSavedReports > 0 ? 'green' : 'red'; + $errorColor = $nbErrorReports > 0 ? 'red' : 'green'; + $savedIcon = $nbSavedReports > 0 ? '✓' : '✗'; + $errorIcon = $nbErrorReports > 0 ? '✗' : '✓'; + + Console::log(Console::text(' ' . $savedIcon, $savedColor) . self::printResult(' Saved reports: ', $nbSavedReports, $savedColor, '', false)); + Console::log(Console::text(' ' . $errorIcon, $errorColor) . self::printResult(' Invalid reports: ', $nbErrorReports, $errorColor, '', false)); + + if ($nbErrorReports > 0){ + $numberDiplayedReports = 0; + $defaultColor = 'lightyellow'; // reset color for last reports + + foreach ($response->data->invalidReports as $report){ + $input = $report->input ? escapeshellcmd($report->input) : ''; // in case on blank line, IP is null + $line = Console::text(' →', 'red'); + $line .= self::printResult(' Input: ', $input, $defaultColor, '', false); + Console::log($line); + self::printResult(' Error: ', $report->error, $defaultColor); + self::printResult(' Line number: ', $report->rowNumber, $defaultColor); + + // counter + $numberDiplayedReports++; + } + } + Console::log(); + self::printFooter($time); + } + + /** + * Perform a clear-address request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function clearIP(array $arguments) + { + $ip = self::getArgumentValue($arguments,'E', 'clear'); + + self::printTitle(Console::text(' ► Clear reports for IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white')); + + // Peforms request + self::printTempMessage(); + $timeStart = microtime(true); // request startime + $response = self::$api->clearAddress($ip)->getObject(); + $timeEnd = microtime(true); // request end time + $time = $timeEnd - $timeStart; // request time + self::clearTempMessage(); + + // check for errors / empty response + if (self::printErrors($response)){ + self::printFooter($time); + exit(1); + } + + // ✓ Done: print deleted report number + Console::log( + Console::text(' ✓', 'green') . + Console::text(' Successfull clear request for IP: [', 'white') . + Console::text($ip, 'lightyellow') . + Console::text(']', 'white') + ); + + self::printResult(' Deleted reports: ', $response->data->numReportsDeleted ?? 0, 'lightyellow'); + Console::log(); + self::printFooter($time); + } + + /** + * Perform a blacklist request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function getBlacklist(array $arguments) + { + $plainText = self::inArguments($arguments,'p','plaintext'); + + if (!$plainText){ + self::printTitle(Console::text(' ► Get Blacklist ', 'darkgray')); + } + + $limit = self::getNumericParameter($arguments,'l', 'limit', 1000); + $scoreMin = self::getNumericParameter($arguments,'s', 'score', 100); + + if (!$plainText){ + self::printTempMessage(); + } + + // do request + $timeStart = microtime(true); // request startime + $response = self::$api->blacklist($limit, $plainText, $scoreMin); // perform request + $timeEnd = microtime(true); // request end time + $time = $timeEnd - $timeStart; // request time + + if (!$plainText){ + self::clearTempMessage(); + } + + // response could be json on error, while plaintext flag is set + $decodedResponse = $response->getObject(); + + if ($plainText && $response->hasError()){ + exit(1); + } + + if (!$plainText && self::printErrors($decodedResponse)){ + self::printFooter($time); + exit(1); + } + + if ($plainText){ + // echo response "as is" + Console::log($response->getPlaintext()); + + } else { + // print list + self::printResult(' List generated at: ', self::getDate($decodedResponse->meta->generatedAt), 'lightyellow', ''); + Console::log(); + + foreach ($decodedResponse->data as $report){ + $score = empty($report->abuseConfidenceScore) ? 0 : $report->abuseConfidenceScore; + $defaultColor = self::getScoreColor($score); + + $line = Console::text(' →', $defaultColor); + $line .= self::printResult(' IP: ', $report->ipAddress, $defaultColor, '', false); + $line .= self::printResult(' | Last reported at: ', self::getDate($report->lastReportedAt), $defaultColor, '', false); + $line .= Console::text(' | Confidence score: ', 'white'); + $line .= self::getScoreBadge($score); + Console::log($line); + } + + // footer + Console::log(); + self::printFooter($time); + } + } + + /** + * Perform a check-block request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function checkBlock($arguments) + { + $network = self::getArgumentValue($arguments,'K', 'check-block'); + + self::printTitle(Console::text(' ► Check network: ', 'darkgray') . Console::text(escapeshellcmd($network), 'white') . Console::text('', 'darkgray')); + + $maxAge = self::getNumericParameter($arguments, 'd', 'days', 30); + $limit = self::getNumericParameter($arguments,'l', 'limit', 0); // 0 mean no limit + + self::printTempMessage(); + + $timeStart = microtime(true); + $check = self::$api->checkBlock($network, $maxAge)->getObject(); + $timeEnd = microtime(true); + $time = $timeEnd - $timeStart; // request time + self::clearTempMessage(); + + // check for errors / empty response + if (self::printErrors($check)){ + self::printFooter($time); + exit(1); + } + + self::printResult(Console::pad(' Network Address:', 23), $check->data->networkAddress, 'lightyellow'); + self::printResult(Console::pad(' Netmask:', 23), $check->data->netmask, 'lightyellow'); + self::printResult(Console::pad(' Min Address:', 23), $check->data->minAddress, 'lightyellow'); + self::printResult(Console::pad(' Max Address:', 23), $check->data->maxAddress, 'lightyellow'); + self::printResult(Console::pad(' Possible Hosts:', 23), $check->data->numPossibleHosts, 'lightyellow'); + self::printResult(Console::pad(' Address SpaceDesc:', 23), $check->data->addressSpaceDesc, 'lightyellow'); + + // print reported addresses + $nbReports = isset($check->data->reportedAddress) ? count($check->data->reportedAddress) : 0; + + if ($nbReports > 0){ + self::printResult(Console::pad(' Reported addresses:', 23), $nbReports, 'lightyellow'); + $numberDiplayedReports = 0; + $defaultColor = 'lightyellow'; // reset color for last reports + + foreach ($check->data->reportedAddress as $report){ + $score = empty($report->abuseConfidenceScore) ? 0 : $report->abuseConfidenceScore; + $defaultColor = self::getScoreColor($score); // color based on score + + $line = Console::text(' →', $defaultColor); + $line .= self::printResult(' IP: ', $report->ipAddress, $defaultColor, '', false); + $line .= self::printResult(' Country: ', $report->countryCode , $defaultColor, '', false); + $line .= Console::text(' | Confidence score: ', 'white'); + $line .= self::getScoreBadge($score); + $line .= self::printResult(' Total reports: ', $report->numReports, $defaultColor, '', false); + $line .= self::printResult(' Last reported at: ', self::getDate($report->mostRecentReport), $defaultColor, '', false); + Console::log($line); + + // counter + $numberDiplayedReports++; + + if ($numberDiplayedReports === $limit || $numberDiplayedReports === $nbReports) { + $line = Console::text(' (', 'white'); + $line .= Console::text($numberDiplayedReports, 'lightyellow'); + $line .= Console::text('/', 'white'); + $line .= Console::text($nbReports, 'lightyellow'); + $line .= Console::text($numberDiplayedReports > 1 ? ' IPs displayed)': ' IP displayed)', 'white'); + Console::log($line); + break; + } + } + + } else { + // no reports + $day = $maxAge > 1 ? 'in last '. $maxAge . ' days': ' today'; + Console::log( Console::text(' ✓', 'green') . Console::text(' No IP reported ' . $day)); + } + + // footer + Console::log(); + self::printFooter($time); + } + + /** + * Perform a check request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function checkIP($arguments) + { + $ip = self::getArgumentValue($arguments,'C', 'check'); + + self::printTitle(Console::text(' ► Check IP: ', 'darkgray') . Console::text(escapeshellcmd($ip), 'white') . Console::text('', 'darkgray')); + + $verbose = self::inArguments($arguments,'v', 'verbose'); + $maxAge = self::getNumericParameter($arguments, 'd', 'days', 30); + $maxReportsNumber = self::getNumericParameter($arguments,'l', 'limit', 10); + + self::printTempMessage(); + + $timeStart = microtime(true); + $check = self::$api->check($ip, $maxAge, $verbose)->getObject(); + $timeEnd = microtime(true); + $time = $timeEnd - $timeStart; // request time + self::clearTempMessage(); + + // check for errors / empty response + if (self::printErrors($check)){ + self::printFooter($time); + exit(1); + } + + // score and data color (depending of abuseConfidenceScore) + $score = empty($check->data->abuseConfidenceScore) ? 0 : $check->data->abuseConfidenceScore; + $defaultColor = self::getScoreColor($score); + $line = Console::text(Console::pad(' Confidence score:', 23), 'white'); + $line .= self::getScoreBadge($score); + Console::log($line); + +// self::printResult(' isPublic', $check->data->isPublic, $defaultColor); +// self::printResult(' ipVersion', $check->data->ipVersion, $defaultColor); + $line = self::printResult(Console::pad(' Whitelisted:', 23), $check->data->isWhitelisted ? 'true': 'false', $defaultColor, '', false); + $line .= $check->data->isWhitelisted ? Console::text(' ★', 'green') : ''; + Console::log($line); + + self::printResult(Console::pad(' Country code:', 23), $check->data->countryCode, $defaultColor); + + if (!empty($check->data->countryName)){ + self::printResult(Console::pad(' Country name:', 23), $check->data->countryName, $defaultColor); + } + + self::printResult(Console::pad(' ISP:', 23), $check->data->isp, $defaultColor); + + if ($check->data->usageType){ + $line = self::printResult(Console::pad(' Usage type:', 23), $check->data->usageType, $defaultColor, '', false); + $line .= $check->data->usageType === 'Reserved' ? Console::text(' ◆', 'green') : ''; + Console::log($line); + } + + $hostames = implode(', ', array_filter($check->data->hostnames)) ?? null; + if (!empty($hostames)){ + self::printResult(Console::pad(' Hostname(s):', 23), $hostames, $defaultColor); + } + + self::printResult(Console::pad(' Domain:', 23), $check->data->domain, $defaultColor); + + $nbReport = $check->data->totalReports && is_numeric($check->data->totalReports) ? intval($check->data->totalReports) : 0; + + if ($nbReport > 0 ){ + $line = self::printResult(Console::pad(' Total reports:', 23), $nbReport, $defaultColor, '', false); + $line .= self::printResult(' from ', $check->data->numDistinctUsers, $defaultColor, '', false); + $line .= Console::text($nbReport > 0 ? ' distinct users': ' user', 'white'); + Console::log($line); + + } else { + // no reports + $day = $maxAge > 1 ? 'in last '. $maxAge . ' days': ' today'; + Console::log( Console::text(' ✓', 'green') . Console::text(' Not reported ' . $day)); + } + + if (!empty($check->data->lastReportedAt)){ + self::printResult(Console::pad(' Last reported at:', 23), self::getDate($check->data->lastReportedAt), $defaultColor); + } + + // print last reports + if ($verbose){ + $nbLastReports = isset($check->data->reports) ? count($check->data->reports) : 0; + + if ($nbLastReports > 0){ + Console::log(' Last reports:', 'white'); + $numberDiplayedReports = 0; + $defaultColor = 'lightyellow'; // reset color for last reports + + foreach ($check->data->reports as $lastReport){ + $categories = []; + foreach (array_filter($lastReport->categories) as $catId){ + $cat = self::$api->getCategoryNamebyId($catId)[0]; + if ($cat !== false) { + $categories[] = $cat; + } + } + + $line = Console::text(' →', $defaultColor); + $line .= self::printResult(' reported at: ', self::getDate($lastReport->reportedAt), $defaultColor, '', false); + // $line .= self::printResult(' by user: ', $lastReport->reporterId, $defaultColor, '', false); + if (isset($lastReport->reporterCountryCode) && isset($lastReport->reporterCountryName)){ + $line .= Console::text(' from: ', 'white'); + $line .= self::printResult('', $lastReport->reporterCountryCode, $defaultColor, '', false); + $line .= Console::text(' - ', 'white'); + $line .= self::printResult('', $lastReport->reporterCountryName, $defaultColor, '', false); + } + $line .= Console::text(' with categor' . (count($categories) > 1 ? "ies: " : "y: "), 'white'); + foreach ($categories as $key => $cat) { + $line .= Console::text($key==0 ? '' : ',' , 'white') . Console::text($cat, $defaultColor); + } + Console::log($line); + + // counter + $numberDiplayedReports++; + if ($numberDiplayedReports === $maxReportsNumber || $numberDiplayedReports === $nbLastReports) { + $line = Console::text(' (', 'white'); + $line .= Console::text($numberDiplayedReports, $defaultColor); + $line .= Console::text('/', 'white'); + $line .= Console::text($nbLastReports, $defaultColor); + $line .= Console::text($numberDiplayedReports > 1 ? ' reports displayed)': ' report displayed)', 'white'); + Console::log($line); + break; + } + } + } + } + + // footer + Console::log(); + self::printFooter($time); + } + +} \ No newline at end of file diff --git a/src/ShellUtils.php b/src/ShellUtils.php new file mode 100644 index 0000000..abd1a55 --- /dev/null +++ b/src/ShellUtils.php @@ -0,0 +1,298 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @version 0.9.8 + * @copyright 2020-2021 Kristuff + */ +namespace Kristuff\AbuseIPDB; + +use Kristuff\Mishell\Console; + +/** + * Class ShellUtils + * + * Absract base class for main cli program + */ +abstract class ShellUtils +{ + /** + * helper functions + */ + use UtilsTrait; + + /** + * Prints title action banner + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function printTitle(string $title) + { + Console::log(); + Console::log($title); + Console::log(); + } + + /** + * Print temp message during api request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function printTempMessage() + { + Console::reLog(Console::text(' ? ', 'green') . Console::text('waiting for api response', 'white') . Console::text(' ... ', 'green')); + } + + /** + * Clear the temp message set during api request + * + * @access protected + * @static + * @param array $arguments + * + * @return void + */ + protected static function clearTempMessage() + { + // long blank string to overwrite previous message + Console::reLog(' '); + } + + /** + * Print app banner + * + * @access protected + * @static + * + * @return void + */ + protected static function printBanner() + { + Console::log(); + Console::log( Console::text(' Kristuff\AbuseIPDB ', 'darkgray') . Console::text(' ' . AbuseIPDBClient::VERSION . ' ', 'white', 'blue')); + Console::log(Console::text(' Made with ', 'darkgray') . Console::text('♥', 'red') . Console::text(' in France', 'darkgray')); + Console::log(' © 2020-2021 Kristuff', 'darkgray'); + Console::log(); + } + + /** + * Print footer + * + * @access protected + * @static + * + * @return void + */ + protected static function printFooter(string $requestTime = '') + { + if (!empty($requestTime)){ + $date_utc = new \DateTime("now", new \DateTimeZone("UTC")); + Console::log( + Console::text(' Request time: ', 'darkgray') . Console::text($requestTime . 's', 'lightgray'). + Console::text(' | UTC time: ', 'darkgray') . Console::text($date_utc->format('Y-m-d H:i:s'), 'lightgray') + ); + } + Console::log(Console::text(' ------------------------------------------------------------------------------------------------------', 'darkgray')); + Console::log( + Console::text(' Kristuff\AbuseIPDB ', 'darkgray') . + Console::text(AbuseIPDBClient::VERSION, 'lightgray') . + Console::text(' | Made with ', 'darkgray') . + Console::text('♥', 'red') . + Console::text(' in France | © 2020-2021 Kristuff (https://github.com/kristuff)', 'darkgray') + ); + Console::log(); + } + + /** + * Prints/gets a result value + * + * @access protected + * @static + * + * @return string + */ + protected static function printResult($text, $value, string $foregroundColor = 'lightred', string $backgroundColor = '', bool $print = true) + { + // do not print null/blank values + if (isset($value)){ + $line = Console::text($text, 'white') . Console::text($value, $foregroundColor, $backgroundColor); + if ($print){ + Console::log($line); + } + return $line; + } + return ''; + } + + /** + * Prints score badge + * + * @access protected + * @static + * @param string $text + * @param int $score + * @param string $textColor + * + * @return string + */ + protected static function getScoreBadge(int $score, string $padding = ' ') + { + $scoreforegroundColor = 'white'; + $scoreBackgroundColor = 'green'; + + if (intval($score) > 0 ){ + $scoreforegroundColor = 'black'; + $scoreBackgroundColor = 'yellow'; + } + if (intval($score) > 50 ){ + $scoreforegroundColor = 'white'; + $scoreBackgroundColor = 'red'; + } + + $badge = str_pad($score, 3, ' ',STR_PAD_LEFT); + return Console::text($padding.$badge.$padding, $scoreforegroundColor, $scoreBackgroundColor); + } + + /** + * Check and print errors in API response + * + * @access protected + * @static + * @param object $response + * @param bool $checkForEmpty + * + * @return bool + */ + protected static function printErrors($response, bool $checkForEmpty = true) + { + if (isset($response) && isset($response->errors)){ + + // top error badge + Console::log(' ' . Console::text(' ERROR ','white', 'red')); + + $num = 0; + // errors is an array, could have more than one error.. + foreach ($response->errors as $err){ + $num++; + + Console::log(Console::text(' ✗', 'red') . self::printResult(' Number: ', $num, 'lightyellow','', false)); + self::printResult(' Status: ', $err->status ?? null, 'lightyellow',''); + + if (!empty($err->source) && !empty($err->source->parameter)){ + self::printResult(' Parameter: ', $err->source->parameter, 'lightyellow'); + } + self::printResult(' Title: ', $err->title ?? null, 'lightyellow'); + self::printResult(' Detail: ', $err->detail ?? null, 'lightyellow'); + + // separate errors + if (count($response->errors) > 1){ + Console::log(' ---'); + } + + } + Console::log(); + return true; + } + + // check for empty response ? + if ( $checkForEmpty && ( empty($response) || empty($response->data)) ){ + self::error('An unexpected error occurred.'); + return true; + } + + return false; + } + + /** + * Print a single error + * + * @access protected + * @static + * @param string $error The error message + * + * @return void + */ + protected static function error($error) + { + // ✗ + Console::log(' ' . Console::text(' ERROR ','white', 'red')); + Console::log( + Console::text(' ✗', 'red') . + Console::text(' Detail: ', 'white') . + Console::text($error, 'lightyellow') . + Console::text('', 'white') + ); + Console::log(); + } + + /** + * helper to validate a condition or exit with an error + * + * @access protected + * @static + * @param bool $condition The condition to evaluate + * @param string $message Error message + * @param bool $print True to print error. Default is true + * + * @return bool + */ + protected static function validate(bool $condition, string $message, bool $print = true) + { + if ( !$condition ){ + if ($print) { + Console::log(); + self::error($message); + self::printFooter(); + } + exit(1); + } + } + + /** + * Get numeric parameter and exit on error + * + * @access protected + * @static + * @param array $arguments + * @param string $shortArg The short argument name + * @param string $longArg The long argument name + * @param int $defaultValue + * + * @return int + */ + protected static function getNumericParameter(array $arguments, string $shortArg, string $longArg, int $defaultValue) + { + if (self::inArguments($arguments,$shortArg, $longArg)){ + $val = self::getArgumentValue($arguments,$shortArg, $longArg); + + if (!is_numeric($val)){ + self::error("Invalid parameter: $longArg must be a numeric value."); + self::printFooter(); + exit(1); + } + return intval($val); + } + return $defaultValue; + } + +} \ No newline at end of file diff --git a/src/UtilsTrait.php b/src/UtilsTrait.php new file mode 100644 index 0000000..c2e35d0 --- /dev/null +++ b/src/UtilsTrait.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @version 0.9.8 + * @copyright 2020-2021 Kristuff + */ +namespace Kristuff\AbuseIPDB; + +/** + * Class Utils + * + */ +trait UtilsTrait +{ + /** + * helper function to get formatted date + * + * @access private + * @static + * @param string $date The UTC date + * + * @return string Formated time + */ + protected static function getDate($date) + { + //2020-05-22T17:06:35+00:00 + return \DateTime::createFromFormat('Y-m-d\TH:i:s+', $date)->format('Y-m-d H:i:s'); + } + + /** + * helper function to get the color corresponding to given score: + * 0 : green + * 1-50 : yellow + * > 50 : lightred + * + * @access protected + * @static + * @param mixed $score + * + * @return string + * + */ + protected static function getScoreColor($score) + { + $score = intval($score); + return $score > 50 ? 'lightred' : ($score > 0 ? 'yellow' : 'green') ; + } + + + /** + * Helper function to get the value of an argument + * + * @access protected + * @static + * @param array $arguments The list of arguments + * @param string $shortArg The short argument name + * @param string $longArg The long argument name + * + * @return string + * + */ + protected static function getArgumentValue(array $arguments, string $shortArg, string $longArg) + { + return (array_key_exists($shortArg, $arguments) ? $arguments[$shortArg] : + (array_key_exists($longArg, $arguments) ? $arguments[$longArg] : '')); + } + + /** + * helper function to check if a argument is given + * + * @access protected + * @static + * @param array $arguments The list of arguments + * @param array $shortArg The short argument name + * @param array $longArg The long argument name + * + * @return bool True if the short or long argument exist in the arguments array, otherwise false + */ + protected static function inArguments($arguments, $shortArg, $longArg) + { + return array_key_exists($shortArg, $arguments) || array_key_exists($longArg, $arguments); + } +} \ No newline at end of file From 3f8ffec30b206eddd54100a94bb512d8df0c4be2 Mon Sep 17 00:00:00 2001 From: kristuff Date: Sun, 17 Jan 2021 21:13:09 +0100 Subject: [PATCH 2/5] change bin dir --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 2b00a57..55df353 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,9 @@ "bin": [ "bin/abuseipdb" ], + "config": { + "bin-dir": "bin" + }, "autoload": { "psr-4": { "Kristuff\\AbuseIPDB\\": "src/" From 30458e6b5717d3adcd169133fa81be177ccc7ad8 Mon Sep 17 00:00:00 2001 From: kristuff Date: Sun, 17 Jan 2021 22:13:24 +0100 Subject: [PATCH 3/5] Update composer.json --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index 55df353..2b00a57 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,6 @@ "bin": [ "bin/abuseipdb" ], - "config": { - "bin-dir": "bin" - }, "autoload": { "psr-4": { "Kristuff\\AbuseIPDB\\": "src/" From 12e58737909e7a1efb5fd030e225e31abafc1e6e Mon Sep 17 00:00:00 2001 From: kristuff Date: Sun, 17 Jan 2021 22:23:12 +0100 Subject: [PATCH 4/5] v0.9.8 --- src/AbuseIPDBClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AbuseIPDBClient.php b/src/AbuseIPDBClient.php index 8acf310..cc14958 100644 --- a/src/AbuseIPDBClient.php +++ b/src/AbuseIPDBClient.php @@ -46,7 +46,7 @@ class AbuseIPDBClient extends ShellUtils const VERSION = 'v0.9.8'; /** - * @var SafeApiHandler $api + * @var SilentApiHandler $api */ private static $api = null; From d1be25ad1ec2b49bd917efcd666276b937f94241 Mon Sep 17 00:00:00 2001 From: kristuff Date: Sun, 17 Jan 2021 22:42:06 +0100 Subject: [PATCH 5/5] v0.9.8 --- src/UtilsTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UtilsTrait.php b/src/UtilsTrait.php index c2e35d0..2dad59a 100644 --- a/src/UtilsTrait.php +++ b/src/UtilsTrait.php @@ -84,12 +84,12 @@ protected static function getArgumentValue(array $arguments, string $shortArg, s * @access protected * @static * @param array $arguments The list of arguments - * @param array $shortArg The short argument name - * @param array $longArg The long argument name + * @param string $shortArg The short argument name + * @param string $longArg The long argument name * * @return bool True if the short or long argument exist in the arguments array, otherwise false */ - protected static function inArguments($arguments, $shortArg, $longArg) + protected static function inArguments(array $arguments, string $shortArg, string $longArg) { return array_key_exists($shortArg, $arguments) || array_key_exists($longArg, $arguments); }