Subversion Repositories vnag

Rev

Rev 52 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * VNag - Nagios Framework for PHP
5
 * Developed by Daniel Marschall, ViaThinkSoft <www.viathinksoft.com>
6
 * Licensed under the terms of the Apache 2.0 license
7
 *
79 daniel-mar 8
 * Revision 2023-10-13
2 daniel-mar 9
 */
10
 
11
declare(ticks=1);
12
 
13
class X509ExpireCheck extends VNag {
14
        protected $argFiles = null;
15
 
16
        public function __construct() {
17
                parent::__construct();
18
 
19
                $this->registerExpectedStandardArguments('Vhtwcv');
20
 
21
                $this->getHelpManager()->setPluginName('check_x509_expire');
79 daniel-mar 22
                $this->getHelpManager()->setVersion('2023-10-13');
2 daniel-mar 23
                $this->getHelpManager()->setShortDescription('This plugin checks X.509 (PEM) files and warns if certificates are about to expire.');
24
                $this->getHelpManager()->setCopyright('Copyright (C) 2011-$CURYEAR$ Daniel Marschall, ViaThinkSoft.');
25
                $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-v] -w <warnSeconds>s -c <critSeconds>s -f "[#]<mask>" [-f "[#]<mask>" [...]]');
26
                $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com');
27
 
28
                // Individual (non-standard) arguments:
29
                $this->addExpectedArgument($this->argFiles = new VNagArgument('f', 'file', VNagArgument::VALUE_REQUIRED, 'mask', 'The files to be checked. This argument can be used multiple times. Wilcards may be used but MUST be passed as string only (not resolved by the Shell). There are two possible checking modes: If you put a # in front of the file mask, only the oldest file of each group will be checked (use this mode e.g. if you have a directory which contains old backups of certificates beside the current working certificate). Otherwise, all files of the file group are checked.'));
30
 
52 daniel-mar 31
                // In this context, when the user writes "-w 60s" then they probably mean "-w @60s". Make sure that the user doesn't do it wrong
2 daniel-mar 32
                $this->warningSingleValueRangeBehaviors[0]  = self::SINGLEVALUE_RANGE_VAL_LT_X_BAD;
33
                $this->criticalSingleValueRangeBehaviors[0] = self::SINGLEVALUE_RANGE_VAL_LT_X_BAD;
34
        }
35
 
36
        private static function humanFriendlyTimeLeft($secs) {
37
                $out = array();
38
 
39
                if ($expired = $secs < 0) $secs *= -1;
40
 
41
                $years = floor($secs / 60 / 60 / 24 / 365);
42
                if ($years > 0) $out[] = $years == 1 ? "$years year" : "$years years";
43
 
44
                $days = floor($secs / 60 / 60 / 24) % 365;
45
                if ($days > 0) $out[] = $days == 1 ? "$days day" : "$days days";
46
 
47
                $hours = floor($secs / 60 / 60) % 24;
48
                if ($hours > 0) $out[] = $hours == 1 ? "$hours hour" : "$hours hours";
49
 
50
                $minutes = floor($secs / 60) % 60;
51
                if ($minutes > 0) $out[] = $minutes == 1 ? "$minutes minute" : "$minutes minutes";
52
 
53
                $seconds = $secs % 60;
54
                if ($seconds > 0) $out[] = $seconds == 1 ? "$seconds second" : "$seconds seconds";
55
 
56
                return ($expired ? 'EXPIRED SINCE ' : '').implode(", ", $out).($expired ? '' : ' left');
57
        }
58
 
59
        private static function timeLeft($pemFile) {
60
                $out = array();
61
 
62
                // TODO: Call PHP's openssl functions instead
63
                exec("openssl x509 -in ".escapeshellarg($pemFile)." -noout -text | grep \"Not After\" | cut -d ':' -f 2-", $out, $code); // TODO: check $code
64
                if ($code != 0) {
65
                        throw new VNagException("Error calling openssl!");
66
                }
67
 
68
                $tim = strtotime($out[0]);
69
                return $tim - time();
70
        }
71
 
72
        protected function cbRun($optional_args=array()) {
73
                $this->argFiles->require();
74
 
75
                $countFilesTotal = 0;
76
                $countFilesCrit = 0;
77
                $countFilesWarn = 0;
78
 
79
                $fileGroupMasks = $this->argFiles->getValue();
80
                if (!is_array($fileGroupMasks)) $fileGroupMasks = array($fileGroupMasks);
81
                foreach ($fileGroupMasks as $fileGroupMask) {
82
                        if (substr($fileGroupMask, 0, 1) === '#') {
83
                                $fileGroupMask = substr($fileGroupMask, 1); // remove #
84
 
85
                                // Mode 1: Only the youngest file of each group is checked.
86
                                // You can use this mode e.g. if you have a folder with downloaded files
87
                                // and you want to check if a downloading-script is still downloading
88
                                // new files regularly.
89
 
90
                                $files = glob($fileGroupMask);
91
                                if (count($files) == 0) continue;
92
 
93
                                $minTimeLeft = null;
94
                                foreach ($files as $file) {
95
                                        $minTimeLeft = is_null($minTimeLeft) ? filemtime($file) : min($minTimeLeft, self::timeLeft($file));
96
                                }
97
 
98
                                $countFilesTotal++;
99
                                if ($this->checkAgainstCriticalRange($minTimeLeft.'s', false, true)) {
100
                                        $countFilesCrit++;
101
                                        $this->addVerboseMessage("File group '$fileGroupMask' oldest file: ".self::humanFriendlyTimeLeft($minTimeLeft)." (Critical)\n", VNag::VERBOSITY_SUMMARY);
102
                                } else if ($this->checkAgainstWarningRange($minTimeLeft.'s', false, true)) {
103
                                        $countFilesWarn++;
104
                                        $this->addVerboseMessage("File group '$fileGroupMask' oldest file: ".self::humanFriendlyTimeLeft($minTimeLeft)." (Warning)\n", VNag::VERBOSITY_SUMMARY);
105
                                } else {
106
                                        if (($this->getArgumentHandler()->getArgumentObj('w')->available()) || ($this->getArgumentHandler()->getArgumentObj('c')->available())) {
107
                                                $this->addVerboseMessage("File group '$fileGroupMask' oldest file: ".self::humanFriendlyTimeLeft($minTimeLeft)." (OK)\n", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
108
                                        } else {
109
                                                $this->addVerboseMessage("File group '$fileGroupMask' oldest file: ".self::humanFriendlyTimeLeft($minTimeLeft)."\n", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
110
                                        }
111
                                }
112
                        } else {
113
                                // Mode 2: All files of each group are checked.
114
 
115
                                $files = glob($fileGroupMask);
116
                                if (count($files) == 0) continue;
117
 
118
                                foreach ($files as $file) {
119
                                        $timeLeft = self::timeLeft($file);
120
                                        $countFilesTotal++;
121
                                        if ($this->checkAgainstCriticalRange($timeLeft.'s', false, true)) {
122
                                                $countFilesCrit++;
123
                                                $this->addVerboseMessage("File $file: ".self::humanFriendlyTimeLeft($timeLeft)." (Critical)\n", VNag::VERBOSITY_SUMMARY);
124
                                        } else if ($this->checkAgainstWarningRange($timeLeft.'s', false, true)) {
125
                                                $countFilesWarn++;
126
                                                $this->addVerboseMessage("File $file: ".self::humanFriendlyTimeLeft($timeLeft)." (Warning)\n", VNag::VERBOSITY_SUMMARY);
127
                                        } else {
128
                                                if (($this->getArgumentHandler()->getArgumentObj('w')->available()) || ($this->getArgumentHandler()->getArgumentObj('c')->available())) {
129
                                                        $this->addVerboseMessage("File $file: ".self::humanFriendlyTimeLeft($timeLeft)." (OK)\n", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
130
                                                } else {
131
                                                        $this->addVerboseMessage("File $file: ".self::humanFriendlyTimeLeft($timeLeft)."\n", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
132
                                                }
133
                                        }
134
                                }
135
                        }
136
                }
137
 
138
                $msg = array();
139
                $msg[] = "Checked $countFilesTotal certificates";
140
                if ($this->getArgumentHandler()->getArgumentObj('w')->available()) $msg[] = "$countFilesWarn are in warning time range";
141
                if ($this->getArgumentHandler()->getArgumentObj('c')->available()) $msg[] = "$countFilesCrit are in critical time range";
142
                $msg = implode(", ", $msg);
143
 
144
                $this->setHeadLine($msg);
145
        }
146
}