├── .gitignore ├── cache └── .gitkeep ├── check.php ├── composer.json ├── composer.lock ├── config.php ├── libraries ├── Cache.php ├── Certificate.php ├── Progress.php └── Whois.php └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | vendor/ 3 | -------------------------------------------------------------------------------- /cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelvardy/expires/c882a2e09f847b9335ba5f65055d6df086e801be/cache/.gitkeep -------------------------------------------------------------------------------- /check.php: -------------------------------------------------------------------------------- 1 | setHeaders(['Domain', 'Domain Expiry', 'Certificate Expiry']); 10 | 11 | $formatColour = function ($expires) { 12 | 13 | $oneMonth = new DateTime(); 14 | $oneMonth->modify('+1 month'); 15 | 16 | $oneWeek = new DateTime(); 17 | $oneWeek->modify('+1 week'); 18 | 19 | return ($expires < $oneWeek ? '%r' : ($expires < $oneMonth ? '%y' : '%g')); 20 | 21 | }; 22 | 23 | print "Checking domains:\n"; 24 | Joelvardy\Progress::bar(1, count($domains)); 25 | 26 | foreach ($domains as $i => $domain) { 27 | 28 | if ($domainDetails = $whois->check($domain->domain)) { 29 | $domainExpiresText = $formatColour($domainDetails->domain->expires) . $domainDetails->domain->expires->format('jS F Y') . '%n'; 30 | } else { 31 | $domainExpiresText = '%rUnable to load whois data%n'; 32 | } 33 | 34 | if (isset($domain->certificate) && $domain->certificate) { 35 | if ($certificateDetails = $certificate->check($domain->domain)) { 36 | $certificateExpiresText = $formatColour($certificateDetails->domain->expires) . $certificateDetails->domain->expires->format('jS F Y') . '%n'; 37 | } else { 38 | $certificateExpiresText = '%rUnable to load certificate data%n'; 39 | } 40 | } else { 41 | $certificateExpiresText = 'N/A'; 42 | } 43 | 44 | $table->addRow([$domain->domain, $domainExpiresText, $certificateExpiresText]); 45 | Joelvardy\Progress::bar(($i + 1), count($domains)); 46 | 47 | } 48 | 49 | $table->display(); 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "novutec/whoisparser": "dev-master", 4 | "wp-cli/php-cli-tools": "dev-master" 5 | }, 6 | "autoload": { 7 | "psr-4": { 8 | "Joelvardy\\": "libraries/" 9 | } 10 | }, 11 | "minimum-stability": "dev" 12 | } 13 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "d23364114301a45bbe0dd6178c2a4475", 8 | "packages": [ 9 | { 10 | "name": "novutec/domainparser", 11 | "version": "dev-master", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/3name/DomainParser.git", 15 | "reference": "c5d0d845e5a88e1c28c9caa78d8834c340f19893" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/3name/DomainParser/zipball/c5d0d845e5a88e1c28c9caa78d8834c340f19893", 20 | "reference": "c5d0d845e5a88e1c28c9caa78d8834c340f19893", 21 | "shasum": "" 22 | }, 23 | "type": "library", 24 | "autoload": { 25 | "classmap": [ 26 | "Parser.php" 27 | ] 28 | }, 29 | "notification-url": "https://packagist.org/downloads/", 30 | "license": [ 31 | "Apache-2.0" 32 | ], 33 | "description": "A domain name parser to parse and to validate a domain name.", 34 | "homepage": "https://github.com/3name/DomainParser/", 35 | "keywords": [ 36 | "domain", 37 | "domainparser", 38 | "php" 39 | ], 40 | "time": "2015-04-11 21:37:57" 41 | }, 42 | { 43 | "name": "novutec/whoisparser", 44 | "version": "dev-master", 45 | "source": { 46 | "type": "git", 47 | "url": "https://github.com/3name/WhoisParser.git", 48 | "reference": "90990514295c747d7700b9f72323fee062e07648" 49 | }, 50 | "dist": { 51 | "type": "zip", 52 | "url": "https://api.github.com/repos/3name/WhoisParser/zipball/90990514295c747d7700b9f72323fee062e07648", 53 | "reference": "90990514295c747d7700b9f72323fee062e07648", 54 | "shasum": "" 55 | }, 56 | "require": { 57 | "novutec/domainparser": ">=2.0.3" 58 | }, 59 | "type": "library", 60 | "autoload": { 61 | "psr-4": { 62 | "Novutec\\WhoisParser\\": "" 63 | } 64 | }, 65 | "notification-url": "https://packagist.org/downloads/", 66 | "license": [ 67 | "Apache-2.0" 68 | ], 69 | "description": "Lookup domain names, IP addresses and AS numbers by WHOIS.", 70 | "homepage": "https://github.com/3name/WhoisParser/", 71 | "keywords": [ 72 | "php", 73 | "whois", 74 | "whoisparser" 75 | ], 76 | "time": "2015-05-21 19:25:53" 77 | }, 78 | { 79 | "name": "wp-cli/php-cli-tools", 80 | "version": "dev-master", 81 | "source": { 82 | "type": "git", 83 | "url": "https://github.com/wp-cli/php-cli-tools.git", 84 | "reference": "288bf4ceeb852bcaa00bf9922b505dccea3b0d39" 85 | }, 86 | "dist": { 87 | "type": "zip", 88 | "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/288bf4ceeb852bcaa00bf9922b505dccea3b0d39", 89 | "reference": "288bf4ceeb852bcaa00bf9922b505dccea3b0d39", 90 | "shasum": "" 91 | }, 92 | "require": { 93 | "php": ">= 5.3.0" 94 | }, 95 | "type": "library", 96 | "autoload": { 97 | "psr-0": { 98 | "cli": "lib/" 99 | }, 100 | "files": [ 101 | "lib/cli/cli.php" 102 | ] 103 | }, 104 | "notification-url": "https://packagist.org/downloads/", 105 | "license": [ 106 | "MIT" 107 | ], 108 | "authors": [ 109 | { 110 | "name": "James Logsdon", 111 | "email": "jlogsdon@php.net", 112 | "role": "Developer" 113 | }, 114 | { 115 | "name": "Daniel Bachhuber", 116 | "email": "daniel@handbuilt.co", 117 | "role": "Maintainer" 118 | } 119 | ], 120 | "description": "Console utilities for PHP", 121 | "homepage": "http://github.com/wp-cli/php-cli-tools", 122 | "keywords": [ 123 | "cli", 124 | "console" 125 | ], 126 | "time": "2015-08-26 03:42:46" 127 | } 128 | ], 129 | "packages-dev": [], 130 | "aliases": [], 131 | "minimum-stability": "dev", 132 | "stability-flags": { 133 | "novutec/whoisparser": 20, 134 | "wp-cli/php-cli-tools": 20 135 | }, 136 | "prefer-stable": false, 137 | "prefer-lowest": false, 138 | "platform": [], 139 | "platform-dev": [] 140 | } 141 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 'shouldwatch.co.uk' 11 | ], 12 | (object) [ 13 | 'domain' => 'request.mx' 14 | ], 15 | (object) [ 16 | 'domain' => 'joelvardy.co.uk' 17 | ], 18 | (object) [ 19 | 'domain' => 'highland-webcams.com', 20 | 'certificate' => true 21 | ], 22 | (object) [ 23 | 'domain' => 'notes.mx', 24 | 'certificate' => true 25 | ], 26 | (object) [ 27 | 'domain' => 'trail.sx', 28 | 'certificate' => true 29 | ], 30 | (object) [ 31 | 'domain' => 'vardy.co' 32 | ], 33 | (object) [ 34 | 'domain' => 'joelvardy.uk' 35 | ], 36 | (object) [ 37 | 'domain' => 'joelvardy.com', 38 | 'certificate' => true 39 | ], 40 | (object) [ 41 | 'domain' => 'demo.joelvardy.com', 42 | 'certificate' => true 43 | ], 44 | (object) [ 45 | 'domain' => 'photos.joelvardy.com', 46 | 'certificate' => true 47 | ], 48 | (object) [ 49 | 'domain' => 'joelgonewild.com', 50 | 'certificate' => true 51 | ] 52 | ]; 53 | -------------------------------------------------------------------------------- /libraries/Cache.php: -------------------------------------------------------------------------------- 1 | cacheDir = $cacheDir; 14 | $this->prefix = $prefix; 15 | } 16 | 17 | protected function cacheKey($domain) 18 | { 19 | return $this->cacheDir . '/' . $this->prefix . '-' . md5($domain) . '.json'; 20 | } 21 | 22 | protected function cacheGet($domain, $cacheValidityTime = 43200) 23 | { 24 | 25 | if (!file_exists($this->cacheKey($domain))) { 26 | // Not found in cache 27 | return false; 28 | } 29 | 30 | $details = json_decode(file_get_contents($this->cacheKey($domain))); 31 | 32 | if (($details->checked + $cacheValidityTime) < time()) { 33 | // Cache is out of date 34 | return false; 35 | } 36 | 37 | return $details; 38 | 39 | } 40 | 41 | protected function cacheSet($domain, $details) 42 | { 43 | return file_put_contents($this->cacheKey($domain), json_encode($details)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /libraries/Certificate.php: -------------------------------------------------------------------------------- 1 | cacheInit($cacheDir, 'certificate'); 11 | } 12 | 13 | protected function format($details) 14 | { 15 | 16 | $date = new \DateTime(); 17 | $date->setTimestamp($details->domain->created); 18 | $details->domain->created = $date; 19 | 20 | $date = new \DateTime(); 21 | $date->setTimestamp($details->domain->expires); 22 | $details->domain->expires = $date; 23 | 24 | return $details; 25 | 26 | } 27 | 28 | public function check($domain) 29 | { 30 | 31 | if ($details = $this->cacheGet($domain)) { 32 | return $this->format($details); 33 | } 34 | 35 | $certFilepath = $this->cacheDir . '/' . md5($domain) . '.crt'; 36 | $response = `echo -n | openssl s_client -servername $domain -connect $domain:443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > $certFilepath`; // Download certificate 37 | $data = openssl_x509_parse(file_get_contents($certFilepath)); 38 | 39 | unlink($certFilepath); // Remove downloaded certificate 40 | 41 | if (!isset($data['validFrom_time_t']) || !isset($data['validTo_time_t'])) { 42 | // Could not load certificate 43 | return false; 44 | } 45 | 46 | $details = (object)[ 47 | 'checked' => time(), 48 | 'domain' => (object)[ 49 | 'created' => $data['validFrom_time_t'], 50 | 'expires' => $data['validTo_time_t'] 51 | ] 52 | ]; 53 | 54 | $this->cacheSet($domain, $details); 55 | 56 | return $this->format($details); 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /libraries/Progress.php: -------------------------------------------------------------------------------- 1 | 0 ? str_repeat('=', $percent - 1) : '') . '>'; 15 | $bar .= str_repeat(' ', 100 - $percent) . "] - $percent% - $done/$total"; 16 | print "\033[0G$bar"; // Note the \033[0G. Put the cursor at the beginning of the line 17 | if ($done >= $total) { 18 | print "\n"; 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /libraries/Whois.php: -------------------------------------------------------------------------------- 1 | cacheInit($cacheDir, 'whois'); 15 | $this->parser = new Parser(); 16 | } 17 | 18 | protected function format($details) 19 | { 20 | 21 | $date = new \DateTime(); 22 | $date->setTimestamp($details->domain->created); 23 | $details->domain->created = $date; 24 | 25 | $date = new \DateTime(); 26 | $date->setTimestamp($details->domain->updated); 27 | $details->domain->updated = $date; 28 | 29 | $date = new \DateTime(); 30 | $date->setTimestamp($details->domain->expires); 31 | $details->domain->expires = $date; 32 | 33 | return $details; 34 | 35 | } 36 | 37 | public function check($domain) 38 | { 39 | 40 | if ($details = $this->cacheGet($domain)) { 41 | return $this->format($details); 42 | } 43 | 44 | $data = $this->parser->lookup($domain); 45 | 46 | if (!isset($data->created) || !isset($data->changed) || !isset($data->expires)) { 47 | // Could not load whos data 48 | return false; 49 | } 50 | 51 | $details = (object)[ 52 | 'checked' => time(), 53 | 'domain' => (object)[ 54 | 'created' => strtotime($data->created), 55 | 'updated' => strtotime($data->changed), 56 | 'expires' => strtotime($data->expires) 57 | ] 58 | ]; 59 | 60 | $this->cacheSet($domain, $details); 61 | 62 | return $this->format($details); 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Expires 2 | 3 | Do you know when your domains and SSL certificates expire? 4 | 5 | ![Expiration dates of my domins](http://i.imgur.com/JovPreA.jpg) 6 | 7 | ## Usage 8 | 9 | * Install dependencies by running `composer install`. 10 | * Update the array of domains in the `./config.php` file. 11 | * Run the checks using the command below. 12 | 13 | ### Run Checks 14 | 15 | The following command will check each domains whois expiration, and if applicable the certificate expiration. 16 | 17 | ```bash 18 | php -f ./check.php 19 | ``` 20 | 21 | *Note: By default whois and certificate data is cached for 24 hours.* 22 | 23 | ## Notes 24 | 25 | Downloading domain certificates uses the local `openssl s_client` command. 26 | 27 | Reading the downloaded certificates also uses the `openssl_x509_parse` function which means PHP should be compiled with the `--with-openssl` flag. 28 | 29 | Build by [Joel Vardy][joelvardy]. 30 | 31 | [joelvardy]: https://joelvardy.com 32 | --------------------------------------------------------------------------------