Subversion Repositories vnag

Rev

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

Rev Author Line No. Line
4 daniel-mar 1
<?php /* <ViaThinkSoftSignature>
23 daniel-mar 2
hlpE5yKfTqwDBnd6/lL8W0naLfVe9aAySNxSTjXQEU4bPneKLKVsA2MkLVp5ES4/b
3
sAFOpWsIV5yqJZskdGesEOqIxiu0bPhFFSPU7pY4+g/CIl6zpq/97HLC+RadiThmh
4
GVUTYAep9111aR0uQNouxAFMXKbzM69/MUsMayF4DAl4yG8TFVHGYd5hWV3h00+Al
5
zoCTWwXs1a6ZXOhPLhq5zMaHypGznVXQNgueW8hSzKJxIbHiE4kkUGLhYpCSLaKMP
6
guIhWKcRGsaTZbuUGnsmgX12vaeA2ExIgvSjvZbx+A1luoIcz5D+JNPO7t+7eOJ8A
7
gcQPoNO9XoRg+gNnH0/1HkRXu8HGNdXn13glpxYnhmFsBPpbuZt+x/soAYH8XmaNQ
8
kUc53u06yce9cNHMPEDRkZ2RoSdb/dANhzOIAXcSbwM0a//x+Ahe0hf7R658frK6O
9
3pMwp5fCj4aam9AvxGx6yJY1x/irxEPwGuF8yRlvcwNSl394H07GavxjRp1jiysjM
10
JHZ1L21IVUgGHfrsEPZugM3KvphfoOrPdnj3CDsEn9vNwOpI6+9yAe4VEbPHToEpi
11
UgtPbmF7gJXFeU0phkqx0ThKDfjCXB7wWBq2Y9MNOKRI/qtSa2X6TY9dW3db6hKzX
12
9VpRWe4LSz9XgIt6LRfMeqKVPlNFyOSLtqDwsgPE5s3XW8FPb356WX+F55EDGDSaC
13
qzT2oypxyHZlyt2bJ1K32Lfqz1TqGmwI+EJT/LpH76RUY0DlrpDArA5WsjVtTaJmK
14
leX91K2zPTKG13MPy9+N1OfuxDa8AVG2FLx/g2Pld7mnrbftTlryYTltEydxvGWAn
15
DD8xFOYz2uwYen3KMuVFEgLvvWWCMZLFEQI5bF6OnB5rNWXJc4nTAqGa8w54bd36z
16
MA7s/Z/ud/U3t1owBgD/6IFw64c76TlAYQjUf223GaI6cS2MVwZwg4hgWwMPpt3je
17
qHBEfN7Xw5vkixz7hyiO2XqcvNfbFzkmZDHHklOC+qlBBGBtJR2wEi/c3wzGNBRDO
18
c5OWrw0F3C5jOh5C0ulzswAgDQa3RzkSXLxIq3yNt8V/Hm9mhuSGQPrYAI9lAntAH
19
WB9yVSQ0pm4DGN+BokpXmGUcQSDine4WjQQ6JKsTQ3BJ/JbjgJQ+btMGJtxbE7mD/
20
BzgYM8dzmPDmcLng6Du9BjrxoSZuOm8PmP8foxTi+8u9m2Zf7GEe6aGVG1QJsaYgX
21
NMTZyCzXpOsfZCUpTzgAwjUpT+dzP6HI7RfFQ8X41t0BqdeyfXzmWVeRhxD2mjL4o
22
Yp3ywdjvTB7FI6NBY7qtWCY4HnILcEbo7++8s7E9J4/bZbe3p13EsCzrETsiF109P
23
g==
4 daniel-mar 24
</ViaThinkSoftSignature> */ ?>
2 daniel-mar 25
<?php
26
 
27
/*
28
 
19 daniel-mar 29
      VNag - Nagios Framework for PHP                  (C) 2014-2020
2 daniel-mar 30
      __     ___      _____ _     _       _     ____         __ _
31
      \ \   / (_) __ |_   _| |__ (_)_ __ | | __/ ___|  ___  / _| |_
32
       \ \ / /| |/ _` || | | '_ \| | '_ \| |/ /\___ \ / _ \| |_| __|
33
        \ V / | | (_| || | | | | | | | | |   <  ___) | (_) |  _| |_
34
         \_/  |_|\__,_||_| |_| |_|_|_| |_|_|\_\|____/ \___/|_|  \__|
35
 
36
      Developed by Daniel Marschall             www.viathinksoft.com
37
      Licensed under the terms of the Apache 2.0 license
23 daniel-mar 38
      Revision 2020-12-06
2 daniel-mar 39
 
40
*/
41
 
42
/****************************************************************************************************
43
 
44
Introduction:
45
 
46
        VNag is a small framework for Nagios Plugin Developers who use PHP CLI scripts.
47
        The main purpose of VNag is to make the development of plugins as easy as possible, so that
48
        the developer can concentrate on the actual work. VNag will try to automate as much
49
        as possible.
50
 
51
        Please note that your script should include the +x chmod flag:
52
                chmod +x myscript.php
53
 
54
        Please see the the demo/ folder for a few examples how to use this framework.
55
 
56
Arguments:
57
 
58
        Example:
59
                $this->addExpectedArgument($argSilent = new VNagArgument('s', 'silent', VNagArgument::VALUE_FORBIDDEN, null,       'Description for the --silent output', $defaultValue));
60
                $this->addExpectedArgument($argHost   = new VNagArgument('H', 'host',   VNagArgument::VALUE_REQUIRED,  'hostname', 'Description for the --host output',   $defaultValue));
61
 
62
        In the example above, the two argument objects $argSilent and $argHost were created.
63
        With these objects of the type VNagArgument, you can query the argument's value,
64
        how often the argument was passed and if it is set:
65
 
66
                $argSilent->count();      // 1 if "-s" is passed, 2 if "-s -s" is passed etc.
67
                $argSilent->available();  // true if "-s" is passed, false otherwise
68
                $argHost->getValue();     // "example.com" if "-h example.com" is passed
69
 
70
        It is recommended that you pass every argument to $this->addExpectedArgument() .
71
        Using this way, VNag can generate a --help page for you, which lists all your arguments.
72
        Future version of VNag may also require to have a complete list of all valid arguments,
73
        since the Nagios Development Guidelines recommend to output the usage information if an illegal
74
        argument is passed. Due to PHP's horrible bad implementation of GNU's getopt(), this check for
75
        unknown arguments is currently not possible, and the developer of VNag does not want to use
76
        dirty hacks/workarounds, which would not match to all argument notation variations/styles.
77
        See: https://bugs.php.net/bug.php?id=68806
78
             https://bugs.php.net/bug.php?id=65673
79
             https://bugs.php.net/bug.php?id=26818
80
 
81
Setting the status:
82
 
83
        You can set the status with:
84
                $this->setStatus(VNag::STATUS_OK);
85
        If you don't set a status, the script will return Unknown instead.
86
        setStatus($status) will keep the most severe status, e.g.
87
                $this->setStatus(VNag::STATUS_CRITICAL);
88
                $this->setStatus(VNag::STATUS_OK);
89
        will result in a status "Critical".
90
        If you want to completely overwrite the status, use $force=true:
91
                $this->setStatus(VNag::STATUS_CRITICAL);
92
                $this->setStatus(VNag::STATUS_OK, true);
93
        The status will now be "OK".
94
 
95
        Possible status codes are:
96
                (For service plugins:)
97
                VNag::STATUS_OK       = 0;
98
                VNag::STATUS_WARNING  = 1;
99
                VNag::STATUS_CRITICAL = 2;
100
                VNag::STATUS_UNKNOWN  = 3;
101
 
102
                (For host plugins:)
103
                VNag::STATUS_UP       = 0;
104
                VNag::STATUS_DOWN     = 1;
105
 
106
Output:
107
 
108
        After the callback function cbRun() of your job has finished,
109
        the framework will automatically output the results in the Nagios console output format,
110
        the visual HTML output and/or the invisible HTML output.
111
 
112
        In case of CLI invokation, the Shell exit code will be remembered and
113
        automatically returned by the shutdown handler once the script normally
114
        terminates. (In case you run different jobs, which is not recommended, the
115
        shutdown handler will output the baddest exit code).
116
 
117
        The Shell output format will be:
118
                <Service status text>: <Comma separates messages> | <whitespace separated primary performance data>
119
                "Verbose information:"
120
                <Multiline verbose output> | <Multiline secondary performance data>
121
 
122
        <Service status text> will be automatically created by VNag.
123
 
124
        Verbose information are printed below the first line. Most Nagios clients will only print the first line.
125
        If you have important output, use $this->setHeadline() instead.
126
        You can add verbose information with following method:
127
                $this->addVerboseMessage('foobar', $verbosity);
128
 
129
        Following verbosity levels are defined:
130
                VNag::VERBOSITY_SUMMARY                = 0; // always printed
131
                VNag::VERBOSITY_ADDITIONAL_INFORMATION = 1; // requires at least -v
132
                VNag::VERBOSITY_CONFIGURATION_DEBUG    = 2; // requiers at least -vv
133
                VNag::VERBOSITY_PLUGIN_DEBUG           = 3; // requiers at least -vvv
134
 
135
        All STDOUT outputs of your script (e.g. by echo) will be interpreted as "verbose" output
136
        and is automatically collected, so
137
                echo "foobar";
138
        has the same functionality as
139
                $this->addVerboseMessage('foobar', VNag::VERBOSITY_SUMMARY);
140
 
141
        You can set messages (which will be added into the first line, which is preferred for plugin outputs)
142
        using
143
                $this->setHeadline($msg, $append, $verbosity);
144
        Using the flag $append, you can choose if you want to append or replace the message.
145
 
146
        VNag will catch Exceptions of your script and will automatically end the plugin,
147
        returning a valid Nagios output.
148
 
149
Automatic handling of basic arguments:
150
 
151
        VNag will automatic handle of following CLI arguments:
152
                -?
153
                -V --version
154
                -h --help
155
                -v --verbose
156
                -t --timeout   (only works if you set declare(ticks=1) at the beginning of each of your scripts)
157
                -w --warning
158
                -c --critical
159
 
160
        You can performe range checking by using:
161
                $example_value = '10MB';
162
                $this->checkAgainstWarningRange($example_value);
163
        this is more or less the same as:
164
                $example_value = '10MB';
165
                $wr = $this->getWarningRange();
166
                if (isset($wr) && $wr->checkAlert($example_value)) {
167
                        $this->setStatus(VNag::STATUS_WARNING);
168
                }
169
 
170
        In case that your script allows ranges which can be relative and absolute, you can provide multiple arguments;
171
        $wr->checkAlert() will be true, as soon as one of the arguments is in the warning range.
172
        The check will be done in this way:
173
                $example_values = array('10MB', '5%');
174
                $this->checkAgainstWarningRange($example_values);
175
        this is more or less the same as:
176
                $example_values = array('10MB', '5%');
177
                $wr = $this->getWarningRange();
178
                if (isset($wr) && $wr->checkAlert($example_values)) {
179
                        $this->setStatus(VNag::STATUS_WARNING);
180
                }
181
 
182
        Note that VNag will automatically detect the UOM (Unit of Measurement) and is also able to convert them,
183
        e.g. if you use the range "-w 20MB:40MB", your script will be able to use $wr->checkAlert('3000KB')
184
 
185
        Please note that only following UOMs are accepted (as defined in the Plugin Development Guidelines):
186
        - no unit specified: assume a number (int or float) of things (eg, users, processes, load averages)
187
        - s, ms, us: seconds
188
        - %: percentage
189
        - B, KB, MB, TB: bytes  // NOTE: GB is not in the official development guidelines,probably due to an error, so I've added them anyway
190
        - c: a continous counter (such as bytes transmitted on an interface)
191
 
192
Multiple warning/critical ranges:
193
 
194
        The arguments -w and -c can have many different values, separated by comma.
195
        We can see this feature e.g. with the official plugin /usr/lib/nagios/plugins/check_ping:
196
        It has following syntax for the arguments -w and -c: <latency>,<packetloss>%
197
 
198
        When you are using checkAgainstWarningRange, you can set the fourth argument to the range number
199
        you would like to check (beginning with 0).
200
 
201
        Example:
202
                // -w 1MB:5MB,5%:10%
203
                $this->checkAgainstWarningRange('4MB', true, true, 0); // check value 4MB against range "1MB:5MB" (no warning)
204
                $this->checkAgainstWarningRange('15%', true, true, 1); // check value 15% gainst range "5%:10%" (gives warning)
205
 
206
Visual HTTP output:
207
 
208
        Can be enabled/disabled with $this->http_visual_output
209
 
210
        Valid values:
211
 
212
        VNag::OUTPUT_SPECIAL   = 1; // illegal usage / help page, version page
213
        VNag::OUTPUT_NORMAL    = 2;
214
        VNag::OUTPUT_EXCEPTION = 4;
215
        VNag::OUTPUT_ALWAYS    = 7;
216
        VNag::OUTPUT_NEVER     = 0;
217
 
218
Encryption and Decryption:
219
 
21 daniel-mar 220
        In case you are emitting machine-readable code in your HTTP output
2 daniel-mar 221
        (can be enabled/disabled by $this->http_invisible_output),
21 daniel-mar 222
        you can encrypt the machine-readable part of your HTTP output by
2 daniel-mar 223
        setting $this->password_out . If you want to read the information,
224
        you need to set $this->password_in at the web-reader plugin.
225
 
226
        You can sign the output by setting $this->privkey with a filename containing
227
        a private key created by OpenSSL. If it is encrypted, please also set
228
        $this->privkey_password .
229
        To check the signature, set $this->pubkey at your web-reader plugin with
230
        the filename of the public key file.
231
 
232
        Attention!
233
        - An empty string is also considered as password. If you don't want to encrypt the
21 daniel-mar 234
          machine-readable output, please set $this->password_out to null.
2 daniel-mar 235
        - Both features (encryption and signatures) require the OpenSSL plugin in PHP.
236
 
237
Performance data:
238
 
239
        You can add performance data using
240
                $this->addPerformanceData(new VNagPerformanceData($label, $value, $warn, $crit, $min, $max));
241
        or by the alternative constructor
242
                $this->addPerformanceData(VNagPerformanceData::createByString("'XYZ'=100;120;130;0;500"));
243
        $value may contain an UOM, e.g. "10MB". All other parameters may not contain an UOM.
244
 
245
Guidelines:
246
 
247
        This framework currently supports meets following guidelines:
248
        - https://nagios-plugins.org/doc/guidelines.html#PLUGOUTPUT (Plugin Output for Nagios)
249
        - https://nagios-plugins.org/doc/guidelines.html#AEN33 (Print only one line of text)
250
        - https://nagios-plugins.org/doc/guidelines.html#AEN41 (Verbose output)
251
        - https://nagios-plugins.org/doc/guidelines.html#AEN78 (Plugin Return Codes)
252
        - https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT (Threshold and ranges)
253
        - https://nagios-plugins.org/doc/guidelines.html#AEN200 (Performance data)
254
        - https://nagios-plugins.org/doc/guidelines.html#PLUGOPTIONS (Plugin Options)
255
        - https://nagios-plugins.org/doc/guidelines.html#AEN302 (Option Processing)
256
          Note: The screen output of the help page will (mostly) be limited to 80 characters width; but the max recommended length of 23 lines cannot be guaranteed.
257
 
258
        This framework does currently NOT support following guidelines:
259
        - https://nagios-plugins.org/doc/guidelines.html#AEN74 (Screen Output)
260
        - https://nagios-plugins.org/doc/guidelines.html#AEN239 (Translations)
261
        - https://nagios-plugins.org/doc/guidelines.html#AEN293 (Use DEFAULT_SOCKET_TIMEOUT)
262
        - https://nagios-plugins.org/doc/guidelines.html#AEN296 (Add alarms to network plugins)
263
        - https://nagios-plugins.org/doc/guidelines.html#AEN245 (Don't execute system commands without specifying their full path)
264
        - https://nagios-plugins.org/doc/guidelines.html#AEN249 (Use spopen() if external commands must be executed)
265
        - https://nagios-plugins.org/doc/guidelines.html#AEN253 (Don't make temp files unless absolutely required)
266
        - https://nagios-plugins.org/doc/guidelines.html#AEN259 (Validate all input)
267
        - https://nagios-plugins.org/doc/guidelines.html#AEN317 (Plugins with more than one type of threshold, or with threshold ranges)
268
 
269
        We will intentionally NOT follow the following guidelines:
270
        - https://nagios-plugins.org/doc/guidelines.html#AEN256 (Don't be tricked into following symlinks)
271
          Reason: We believe that this guideline is contraproductive.
272
                  Nagios plugins usually run as user 'nagios'. It is the task of the system administrator
273
                  to ensure that the user 'nagios' must not read/write to files which are not intended
274
                  for access by the Nagios service. Instead, symlinks are useful for several tasks.
275
                  See also http://stackoverflow.com/questions/27112949/nagios-plugins-why-not-following-symlinks
276
 
277
VNag over HTTP:
278
 
279
        A script that uses the VNag framework can run as CLI script (normal Nagios plugin) or as web site (or both).
280
        Having the script run as website, you can include a Nagios information combined with a human friendly HTML output which can
281
        include colors, graphics (like charts) etc.
282
 
283
        For example:
284
        A script that measures traffic can have a website which shows graphs,
285
        and has a hidden Nagios output included, which can be read by a Nagios plugin that
286
        converts the hidden information on that website into an output that Nagios can evaluate.
287
 
288
        Here is a comparison of the usage and behavior of VNag in regards to CLI and HTTP calls:
289
 
290
        ------------------------------------------------------------------------------------------
291
          CLI script                                 HTTP script
292
        ------------------------------------------------------------------------------------------
293
        * "echo" will be discarded.                  * "echo" output will be discarded.
294
 
295
        * Exceptions will be handled.                * Exceptions will be handled.
296
 
297
        * outputHTML() will be ignored.              * outputHTML() will be handled.
298
          (This allows you to have the same script
299
          running as CLI and HTML)
300
 
301
        * Arguments are passed via CLI.              * Arguments are passed via $_REQUEST
302
                                                       (i.e. GET or POST)
303
 
304
        * Arguments: "-vvv"                          * Arguments: GET ?v[]=&v[]=&v[]= or POST
305
 
306
        * When run() has finished, the program       * When run() has finished, the program
307
          flow continues, although it is not           flow continues.
308
          recommended that you do anything after it.
309
          (The exit code is remembered for the
310
           shutdown handler)
311
 
312
        * Exactly 1 job must be called, resulting     * You can call as many jobs as you want.
313
          in a single output of that job.               A website can include more than one
314
                                                        Nagios output which are enumerated with
315
                                                        a serial number (0,1,2,3,...) or manual ID.
316
        ------------------------------------------------------------------------------------------
317
 
318
****************************************************************************************************/
319
 
22 daniel-mar 320
if (!VNag::is_http_mode()) error_reporting(E_ALL);
2 daniel-mar 321
 
322
# If you want to use -t/--timeout with your module, you must add following line in your module code:
323
// WONTFIX: declare(ticks=1) is deprecated? http://www.hackingwithphp.com/4/21/0/the-declare-function-and-ticks
324
// WONTFIX: check is the main script used declare(ticks=1). (Not possible in PHP)
325
declare(ticks=1);
326
 
327
# Attention: The -t/--timeout parameter does not respect the built-in set_time_limit() of PHP.
328
# PHP should set this time limit to infinite.
329
set_time_limit(0);
330
 
13 daniel-mar 331
define('VNAG_JSONDATA_V1', 'oid:1.3.6.1.4.1.37476.2.3.1.1'); // {iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 products(2) vnag(3) jsondata(1) v1(1)}
2 daniel-mar 332
 
333
// Set this to an array to overwrite getopt() and $_REQUEST[], respectively.
334
// Useful for mock tests.
335
$OVERWRITE_ARGUMENTS = null;
336
 
337
function _empty($x) {
338
        // Returns true for '' or null. Does not return true for value 0 or '0' (like empty() does)
339
        return trim($x) == '';
340
}
341
 
342
abstract class VNag {
20 daniel-mar 343
        /*public*/ const VNAG_VERSION = '2020-04-21';
2 daniel-mar 344
 
345
        // Status 0..3 for STATUSMODEL_SERVICE (the default status model):
346
        # The guideline states: "Higher-level errors (such as name resolution errors, socket timeouts, etc) are outside of the control of plugins and should generally NOT be reported as UNKNOWN states."
347
        # We choose 4 as exitcode. The plugin developer is free to return any other status.
20 daniel-mar 348
        /*public*/ const STATUS_OK       = 0;
349
        /*public*/ const STATUS_WARNING  = 1;
350
        /*public*/ const STATUS_CRITICAL = 2;
351
        /*public*/ const STATUS_UNKNOWN  = 3;
352
        /*public*/ const STATUS_ERROR    = 4; // and upwards
2 daniel-mar 353
 
354
        // Status 0..1 for STATUSMODEL_HOST:
355
        // The page https://blog.centreon.com/good-practices-how-to-develop-monitoring-plugin-nagios/
356
        // states that host plugins may return following status codes:
357
        // 0=UP, 1=DOWN, Other=Maintains last known state
20 daniel-mar 358
        /*public*/ const STATUS_UP       = 0;
359
        /*public*/ const STATUS_DOWN     = 1;
360
        /*public*/ const STATUS_MAINTAIN = 2; // and upwards
2 daniel-mar 361
 
20 daniel-mar 362
        /*public*/ const VERBOSITY_SUMMARY                = 0;
363
        /*public*/ const VERBOSITY_ADDITIONAL_INFORMATION = 1;
364
        /*public*/ const VERBOSITY_CONFIGURATION_DEBUG    = 2;
365
        /*public*/ const VERBOSITY_PLUGIN_DEBUG           = 3;
366
        /*public*/ const MAX_VERBOSITY = self::VERBOSITY_PLUGIN_DEBUG;
2 daniel-mar 367
 
20 daniel-mar 368
        /*public*/ const STATUSMODEL_SERVICE = 0;
369
        /*public*/ const STATUSMODEL_HOST    = 1;
2 daniel-mar 370
 
371
        private $initialized = false;
372
 
373
        private $status = null;
374
        private $messages = array(); // array of messages which are all put together into the headline, comma separated
375
        private $verbose_info = ''; // all other lines
376
        private $warningRanges = array();
377
        private $criticalRanges = array();
378
        private $performanceDataObjects = array();
379
        private static $exitcode = 0;
380
 
381
        private $helpObj = null;
382
        private $argHandler = null;
383
 
20 daniel-mar 384
        /*public*/ const OUTPUT_NEVER     = 0;
385
        /*public*/ const OUTPUT_SPECIAL   = 1; // illegal usage / help page, version page
386
        /*public*/ const OUTPUT_NORMAL    = 2;
387
        /*public*/ const OUTPUT_EXCEPTION = 4;
388
        /*public*/ const OUTPUT_ALWAYS    = 7; // = OUTPUT_SPECIAL+OUTPUT_NORMAL+OUTPUT_EXCEPTION
2 daniel-mar 389
 
19 daniel-mar 390
        public $http_visual_output    = self::OUTPUT_ALWAYS; // show a human-readable panel? ...
391
        public $http_invisible_output = self::OUTPUT_ALWAYS; // ... and/or show an invisible machine-readable tag?
2 daniel-mar 392
 
393
        // $html_before and $html_after contain the output HTML which were sent by the user
394
 
395
        // before and after the visual output
396
        protected $html_before = '';
397
        protected $html_after = '';
398
 
399
        protected $statusmodel = self::STATUSMODEL_SERVICE;
400
 
401
        protected $show_status_in_headline = true;
402
 
403
        protected $default_status = self::STATUS_UNKNOWN;
404
        protected $default_warning_range = null;
405
        protected $default_critical_range = null;
406
 
407
        protected $argWarning;
408
        protected $argCritical;
409
        protected $argVersion;
410
        protected $argVerbosity;
411
        protected $argTimeout;
412
        protected $argHelp;
413
        protected $argUsage;
414
 
415
        // -----------------------------------------------------------
416
 
21 daniel-mar 417
        // The ID will be used for writing AND reading of the machine-readable
2 daniel-mar 418
        // Nagios output embedded in a website. (A web-reader acts as proxy, so the
419
        // input and output ID will be equal)
420
        // Attention: Once you run run(), $id will be "used" and resetted to null.
421
        // The ID can be any string, e.g. a GUID, an OID, a package name or something else.
422
        // It should be unique. If you don't set an ID, a serial number (0, 1, 2, 3, ...) will be
423
        // used for your outputs.
424
        public $id = null;
425
        protected static $http_serial_number = 0;
426
 
427
        // -----------------------------------------------------------
428
 
21 daniel-mar 429
        // Private key: Optional feature used in writeInvisibleHTML (called by run in HTTP mode) in order to sign/encrypt the output
2 daniel-mar 430
        public $privkey = null;
431
        public $privkey_password = null;
21 daniel-mar 432
        public $sign_algo = null; // default: OPENSSL_ALGO_SHA256
2 daniel-mar 433
 
21 daniel-mar 434
        // Public key: Optional feature used in a web-reader [readInvisibleHTML) to check the integrity of a message
2 daniel-mar 435
        public $pubkey = null;
436
 
437
        // -----------------------------------------------------------
438
 
439
        // These settings should be set by derivated classes where the user intuitively expects the
440
        // warning (w) or critical (c) parameter to mean something else than defined in the development guidelines.
441
        // Usually, the single value "-w X" means the same like "-w X:X", which means everything except X is bad.
442
        // This behavior is VNag::SINGLEVALUE_RANGE_DEFAULT.
443
        // But for plugins e.g. for checking disk space, the user expects the argument "-w X" to mean
444
        // "everything below X is bad" (if X is defined as free disk space).
445
        // So we would choose the setting VNag::SINGLEVALUE_RANGE_VAL_LT_X_BAD.
446
        // Note: This setting is implemented as array, so that each range number (in case you want to have more
447
        //       than one range, like in the PING plugin that checks latency and package loss)
448
        //       can have its individual behavior for single values.
449
        protected $warningSingleValueRangeBehaviors  = array(self::SINGLEVALUE_RANGE_DEFAULT);
450
        protected $criticalSingleValueRangeBehaviors = array(self::SINGLEVALUE_RANGE_DEFAULT);
451
 
452
        // Default behavior according to the development guidelines:
453
        //  x means  x:x, which means, everything except x% is bad.
454
        // @x means @x:x, which means, x is bad and everything else is good.
455
        const SINGLEVALUE_RANGE_DEFAULT = 0;
456
 
457
        // The single value x means, everything > x is bad. @x is not defined.
458
        const SINGLEVALUE_RANGE_VAL_GT_X_BAD = 1;
459
 
460
        // The single value x means, everything >= x is bad. @x is not defined.
461
        const SINGLEVALUE_RANGE_VAL_GE_X_BAD = 2;
462
 
463
        // The single value x means, everything < x is bad. @x is not defined.
464
        const SINGLEVALUE_RANGE_VAL_LT_X_BAD = 3;
465
 
466
        // The single value x means, everything <= x is bad. @x is not defined.
467
        const SINGLEVALUE_RANGE_VAL_LE_X_BAD = 4;
468
 
469
        // -----------------------------------------------------------
470
 
471
        // Encryption password: Optional feature used in writeInvisibleHTML (called by run in HTTP mode)
472
        public $password_out = null;
473
 
21 daniel-mar 474
        // Decryption password: Used in readInvisibleHTML to decrypt an encrypted machine-readable info
2 daniel-mar 475
        public $password_in = null;
476
 
477
        // -----------------------------------------------------------
478
 
479
        public static function is_http_mode() {
480
                return php_sapi_name() !== 'cli';
481
        }
482
 
483
        public function getHelpManager() {
484
                return $this->helpObj;
485
        }
486
 
487
        public function getArgumentHandler() {
488
                return $this->argHandler;
489
        }
490
 
491
        public function outputHTML($text, $after_visual_output=true) {
492
                if ($this->is_http_mode()) {
493
                        if ($this->initialized) {
494
                                if ($after_visual_output) {
495
                                        $this->html_after .= $text;
496
                                } else {
497
                                        $this->html_before .= $text;
498
                                }
499
                        } else {
500
                                echo $text;
501
                        }
502
                }
503
        }
504
 
505
        protected function resetArguments() {
506
                $this->argWarning   = null;
507
                $this->argCritical  = null;
508
                $this->argVersion   = null;
509
                $this->argVerbosity = null;
510
                $this->argTimeout   = null;
511
                $this->argHelp      = null;
512
                // $this->argUsage  = null;
513
 
514
                // Also remove cache
23 daniel-mar 515
                $this->argWarning   = null;
516
                $this->argCritical  = null;
2 daniel-mar 517
        }
518
 
519
        // e.g. $args = "wcVvht"
520
        public function registerExpectedStandardArguments($args) {
521
                $this->resetArguments();
522
 
523
                for ($i=0; $i<strlen($args); $i++) {
524
                        switch ($args[$i]) {
525
                                case 'w':
526
                                        $this->addExpectedArgument($this->argWarning   = new VNagArgument('w', 'warning',  VNagArgument::VALUE_REQUIRED,  VNagLang::$argname_value, VNagLang::$warning_range));
527
                                        break;
528
                                case 'c':
529
                                        $this->addExpectedArgument($this->argCritical  = new VNagArgument('c', 'critical', VNagArgument::VALUE_REQUIRED,  VNagLang::$argname_value, VNagLang::$critical_range));
530
                                        break;
531
                                case 'V':
532
                                        $this->addExpectedArgument($this->argVersion   = new VNagArgument('V', 'version',  VNagArgument::VALUE_FORBIDDEN, null, VNagLang::$prints_version));
533
                                        break;
534
                                case 'v':
535
                                        // In HTTP: -vvv is &v[]=&v[]=&v[]=
536
                                        $this->addExpectedArgument($this->argVerbosity = new VNagArgument('v', 'verbose',  VNagArgument::VALUE_FORBIDDEN, null, VNagLang::$verbosity_helptext));
537
                                        break;
538
                                case 't':
539
                                        // Attention: not every plugin supports it because of declare(ticks=1) needs to be written in the main script
540
                                        $this->addExpectedArgument($this->argTimeout   = new VNagArgument('t', 'timeout',  VNagArgument::VALUE_REQUIRED,  VNagLang::$argname_seconds, VNagLang::$timeout_helptext));
541
                                        break;
542
                                // case '?':
543
                                case 'h':
544
                                        $this->addExpectedArgument($this->argHelp      = new VNagArgument('h', 'help',     VNagArgument::VALUE_FORBIDDEN, null, VNagLang::$help_helptext));
545
                                        break;
546
                                default:
547
                                        $letter = $args[$i];
548
                                        throw new VNagInvalidStandardArgument(sprintf(VNagLang::$no_standard_arguments_with_letter, $letter));
549
                                        break;
550
                        }
551
                }
552
        }
553
 
554
        public function addExpectedArgument($argObj) {
555
                // Emulate C++ "friend" access to hidden functions
556
 
557
                // $this->helpObj->_addOption($argObj);
558
                $helpObjAddEntryMethod = new ReflectionMethod($this->helpObj, '_addOption');
559
                $helpObjAddEntryMethod->setAccessible(true);
560
                $helpObjAddEntryMethod->invoke($this->helpObj, $argObj);
561
 
562
                // $this->argHandler->_addExpectedArgument($argObj);
563
                $argHandlerAddEntryMethod = new ReflectionMethod($this->argHandler, '_addExpectedArgument');
564
                $argHandlerAddEntryMethod->setAccessible(true);
565
                $argHandlerAddEntryMethod->invoke($this->argHandler, $argObj);
566
        }
567
 
568
        protected function createArgumentHandler() {
569
                $this->argHandler = new VNagArgumentHandler();
570
        }
571
 
572
        protected function createHelpObject() {
573
                $this->helpObj = new VNagHelp();
574
        }
575
 
576
        protected function checkInitialized() {
577
                if (!$this->initialized) throw new VNagFunctionCallOutsideSession();
578
        }
579
 
580
        protected function getVerbosityLevel() {
581
                $this->checkInitialized(); // if (!$this->initialized) return false;
582
 
583
                if (!isset($this->argVerbosity)) {
584
                        //The verbose argument is always optional
585
                        //throw new VNagRequiredArgumentNotRegistered('-v');
586
                        return self::VERBOSITY_SUMMARY;
587
                } else {
588
                        $level = $this->argVerbosity->count();
589
                        if ($level > self::MAX_VERBOSITY) $level = self::MAX_VERBOSITY;
590
                        return $level;
591
                }
592
        }
593
 
594
        public function getWarningRange($argumentNumber=0) {
595
                $this->checkInitialized(); // if (!$this->initialized) return false;
596
 
597
                if (!isset($this->warningRanges[$argumentNumber])) {
598
                        if (!is_null($this->argWarning)) {
599
                                $warning = $this->argWarning->getValue();
600
                                if (!is_null($warning)) {
601
                                        $vals = explode(',',$warning);
602
                                        foreach ($vals as $number => $val) {
603
                                                if (_empty($val)) {
604
                                                        $this->warningRanges[$number] = null;
605
                                                } else {
606
                                                        $singleValueBehavior = isset($this->warningSingleValueRangeBehaviors[$number]) ? $this->warningSingleValueRangeBehaviors[$number] : VNag::SINGLEVALUE_RANGE_DEFAULT;
607
                                                        $this->warningRanges[$number] = new VNagRange($val, $singleValueBehavior);
608
                                                }
609
                                        }
610
                                } else {
611
                                        $this->warningRanges[0] = $this->default_warning_range;
612
                                }
613
                        } else {
614
                                return null;
615
                        }
616
                }
617
 
618
                if (isset($this->warningRanges[$argumentNumber])) {
619
                        return $this->warningRanges[$argumentNumber];
620
                } else {
621
                        return null;
622
                }
623
        }
624
 
625
        public function getCriticalRange($argumentNumber=0) {
626
                $this->checkInitialized(); // if (!$this->initialized) return false;
627
 
628
                if (!isset($this->criticalRanges[$argumentNumber])) {
629
                        if (!is_null($this->argCritical)) {
630
                                $critical = $this->argCritical->getValue();
631
                                if (!is_null($critical)) {
632
                                        $vals = explode(',',$critical);
633
                                        foreach ($vals as $number => $val) {
634
                                                $singleValueBehavior = isset($this->criticalSingleValueRangeBehaviors[$number]) ? $this->criticalSingleValueRangeBehaviors[$number] : VNag::SINGLEVALUE_RANGE_DEFAULT;
635
                                                $this->criticalRanges[$number] = new VNagRange($val, $singleValueBehavior);
636
                                        }
637
                                } else {
638
                                        $this->criticalRanges[0] = $this->default_critical_range;
639
                                }
640
                        } else {
641
                                return null;
642
                        }
643
                }
644
 
645
                if (isset($this->criticalRanges[$argumentNumber])) {
646
                        return $this->criticalRanges[$argumentNumber];
647
                } else {
648
                        return null;
649
                }
650
        }
651
 
652
        public function checkAgainstWarningRange($values, $force=true, $autostatus=true, $argumentNumber=0) {
653
                $this->checkInitialized(); // if (!$this->initialized) return;
654
 
655
                if (!$this->getArgumentHandler()->isArgRegistered('w')) {
656
                        // Developer's mistake: The argument is not in the list of expected arguments
657
                        throw new VNagRequiredArgumentNotRegistered('-w');
658
                }
659
 
660
                $wr = $this->getWarningRange($argumentNumber);
661
                if (isset($wr)) {
662
                        if ($wr->checkAlert($values)) {
663
                                if ($autostatus) $this->setStatus(VNag::STATUS_WARNING);
664
                                return true;
665
                        } else {
666
                                if ($autostatus) $this->setStatus(VNag::STATUS_OK);
667
                                return false;
668
                        }
669
                } else {
670
                        if ($force) {
671
                                // User's mistake: They did not pass the argument to the plugin
672
                                if (($argumentNumber > 0) && (count($this->warningRanges) > 0)) {
673
                                        throw new VNagInvalidArgumentException(sprintf(VNagLang::$too_few_warning_ranges, $argumentNumber+1));
674
                                } else {
675
                                        throw new VNagRequiredArgumentMissing('-w');
676
                                }
677
                        }
678
                }
679
        }
680
 
681
        public function checkAgainstCriticalRange($values, $force=true, $autostatus=true, $argumentNumber=0) {
682
                $this->checkInitialized(); // if (!$this->initialized) return;
683
 
684
                if (!$this->getArgumentHandler()->isArgRegistered('c')) {
685
                        // Developer's mistake: The argument is not in the list of expected arguments
686
                        throw new VNagRequiredArgumentNotRegistered('-c');
687
                }
688
 
689
                $cr = $this->getCriticalRange($argumentNumber);
690
                if (isset($cr)) {
691
                        if ($cr->checkAlert($values)) {
692
                                if ($autostatus) $this->setStatus(VNag::STATUS_CRITICAL);
693
                                return true;
694
                        } else {
695
                                if ($autostatus) $this->setStatus(VNag::STATUS_OK);
696
                                return false;
697
                        }
698
                } else {
699
                        if ($force) {
700
                                // User's mistake: They did not pass the argument to the plugin
701
                                if (($argumentNumber > 0) && (count($this->warningRanges) > 0)) {
702
                                        throw new VNagInvalidArgumentException(sprintf(VNagLang::$too_few_critical_ranges, $argumentNumber+1));
703
                                } else {
704
                                        throw new VNagRequiredArgumentMissing('-c');
705
                                }
706
                        }
707
                }
708
        }
709
 
710
        protected static function getBaddestExitcode($code1, $code2) {
711
                return max($code1, $code2);
712
        }
713
 
714
        # DO NOT CALL MANUALLY
715
        # Unfortunately, this function has to be public, otherwise register_shutdown_function() wouldn't work
716
        public static function _shutdownHandler() {
717
                if (!self::is_http_mode()) {
718
                        exit((int)self::$exitcode);
719
                }
720
        }
721
 
722
        protected function _exit($code) {
723
                self::$exitcode = $this->getBaddestExitcode($code, self::$exitcode);
724
        }
725
 
726
        private $constructed = false;
727
        function __construct() {
728
                $this->createHelpObject();
729
                $this->createArgumentHandler();
730
 
731
                $this->addExpectedArgument($this->argUsage = new VNagArgument('?', '', VNagArgument::VALUE_FORBIDDEN, null, VNagLang::$prints_usage));
732
 
733
                $this->constructed = true;
734
        }
735
 
736
        function __destruct() {
737
                if (Timeouter::started()) {
738
                        Timeouter::end();
739
                }
740
        }
741
 
742
        // $optional_args will be forwarded to the callback function cbRun()
743
        public function run($optional_args=array()) {
19 daniel-mar 744
                global $inside_vnag_run;
20 daniel-mar 745
 
19 daniel-mar 746
                $inside_vnag_run = true;
2 daniel-mar 747
                try {
19 daniel-mar 748
                        if (!$this->constructed) {
749
                                throw new VNagNotConstructed(VNagLang::$notConstructed);
750
                        }
2 daniel-mar 751
 
19 daniel-mar 752
                        try {
753
                                $this->initialized = true;
754
                                $this->html_before = '';
755
                                $this->html_after = '';
756
                                $this->setStatus(null, true);
757
                                $this->messages = array();
2 daniel-mar 758
 
19 daniel-mar 759
                                register_shutdown_function(array($this, '_shutdownHandler'));
2 daniel-mar 760
 
19 daniel-mar 761
                                if ($this->argHandler->illegalUsage()) {
762
                                        $content = $this->helpObj->printUsagePage();
763
                                        $this->setStatus(VNag::STATUS_UNKNOWN);
764
 
765
                                        if ($this->is_http_mode()) {
766
                                                echo $this->html_before;
767
                                                if ($this->http_visual_output    & VNag::OUTPUT_SPECIAL) echo $this->writeVisualHTML($content);
768
                                                if ($this->http_invisible_output & VNag::OUTPUT_SPECIAL) echo $this->writeInvisibleHTML($content);
769
                                                echo $this->html_after;
770
                                                return; // cancel
771
                                        } else {
772
                                                echo $content;
773
                                                return $this->_exit($this->status);
774
                                        }
2 daniel-mar 775
                                }
776
 
19 daniel-mar 777
                                if (!is_null($this->argVersion) && ($this->argVersion->available())) {
778
                                        $content = $this->helpObj->printVersionPage();
779
                                        $this->setStatus(VNag::STATUS_UNKNOWN);
2 daniel-mar 780
 
19 daniel-mar 781
                                        if ($this->is_http_mode()) {
782
                                                echo $this->html_before;
783
                                                if ($this->http_visual_output    & VNag::OUTPUT_SPECIAL) echo $this->writeVisualHTML($content);
784
                                                if ($this->http_invisible_output & VNag::OUTPUT_SPECIAL) echo $this->writeInvisibleHTML($content);
785
                                                echo $this->html_after;
786
                                                return; // cancel
787
                                        } else {
788
                                                echo $content;
789
                                                return $this->_exit($this->status);
790
                                        }
2 daniel-mar 791
                                }
792
 
19 daniel-mar 793
                                if (!is_null($this->argHelp) && ($this->argHelp->available())) {
794
                                        $content = $this->helpObj->printHelpPage();
795
                                        $this->setStatus(VNag::STATUS_UNKNOWN);
2 daniel-mar 796
 
19 daniel-mar 797
                                        if ($this->is_http_mode()) {
798
                                                echo $this->html_before;
799
                                                if ($this->http_visual_output    & VNag::OUTPUT_SPECIAL) echo $this->writeVisualHTML($content);
800
                                                if ($this->http_invisible_output & VNag::OUTPUT_SPECIAL) echo $this->writeInvisibleHTML($content);
801
                                                echo $this->html_after;
802
                                                return; // cancel
803
                                        } else {
804
                                                echo $content;
805
                                                return $this->_exit($this->status);
806
                                        }
2 daniel-mar 807
                                }
808
 
19 daniel-mar 809
                                // Initialize ranges (and check their validity)
810
                                $this->getWarningRange();
811
                                $this->getCriticalRange();
2 daniel-mar 812
 
19 daniel-mar 813
                                if (!is_null($this->argTimeout)) {
814
                                        $timeout = $this->argTimeout->getValue();
815
                                        if (!is_null($timeout)) {
816
                                                Timeouter::start($timeout);
817
                                        }
2 daniel-mar 818
                                }
819
 
19 daniel-mar 820
                                ob_start();
821
                                $init_ob_level = ob_get_level();
822
                                try {
823
                                        $this->cbRun($optional_args);
2 daniel-mar 824
 
19 daniel-mar 825
                                        // This will NOT be put in the 'finally' block, because otherwise it would trigger if an Exception happened (Which clears the OB)
826
                                        if (ob_get_level() < $init_ob_level) throw new VNagImplementationErrorException(VNagLang::$output_level_lowered);
827
                                } finally {
828
                                        while (ob_get_level() > $init_ob_level) @ob_end_clean();
829
                                }
2 daniel-mar 830
 
19 daniel-mar 831
                                if (is_null($this->status)) $this->setStatus($this->default_status,true);
2 daniel-mar 832
 
19 daniel-mar 833
                                $outputType = VNag::OUTPUT_NORMAL;
834
                        } catch (Exception $e) {
835
                                $this->handleException($e);
836
                                $outputType = VNag::OUTPUT_EXCEPTION;
837
                        }
2 daniel-mar 838
 
19 daniel-mar 839
                        if ($this->is_http_mode()) {
840
                                echo $this->html_before;
841
                                if ($this->http_invisible_output & $outputType) {
842
                                        echo $this->writeInvisibleHTML();
843
                                }
844
                                if ($this->http_visual_output & $outputType) {
845
                                        echo $this->writeVisualHTML();
846
                                }
847
                                echo $this->html_after;
848
                        } else {
849
                                echo $this->getNagiosConsoleText();
850
                                return $this->_exit($this->status);
2 daniel-mar 851
                        }
19 daniel-mar 852
 
853
                        Timeouter::end();
854
                } finally {
855
                        $inside_vnag_run = false;
2 daniel-mar 856
                }
857
        }
858
 
859
        private function getNagiosConsoleText() {
860
                // see https://nagios-plugins.org/doc/guidelines.html#AEN200
861
                // 1. space separated list of label/value pairs
862
                $ary_perfdata = $this->getPerformanceData();
863
                $performancedata_first = array_shift($ary_perfdata);
864
                $performancedata_rest  = implode(' ', $ary_perfdata);
865
 
866
                $status_text = VNagLang::status($this->status, $this->statusmodel);
867
                if (_empty($this->getHeadline())) {
868
                        $content = $status_text;
869
                } else {
870
                        if ($this->show_status_in_headline) {
871
                                $content = $status_text.': '.$this->getHeadline();
872
                        } else {
873
                                $content = $this->getHeadline();
874
                        }
875
                }
876
 
877
                if (!_empty($performancedata_first)) $content .= '|'.trim($performancedata_first);
878
                $content .= "\n";
879
                if (!_empty($this->verbose_info)) {
880
                        //$content .= "\n".VNagLang::$verbose_info.":\n\n";
881
                        $content .= trim($this->verbose_info);
882
                }
883
                if (!_empty($performancedata_rest)) $content .= '|'.trim($performancedata_rest);
884
                $content .= "\n";
885
 
886
                return trim($content)."\n";
887
        }
888
 
889
        abstract protected function cbRun();
890
 
891
        public function addPerformanceData($prefDataObj, $move_to_font=false, $verbosityLevel=VNag::VERBOSITY_SUMMARY) {
892
                $this->checkInitialized(); // if (!$this->initialized) return;
893
 
894
                if ((!isset($this->argVerbosity)) && ($verbosityLevel > VNag::VERBOSITY_SUMMARY)) throw new VNagRequiredArgumentNotRegistered('-v');
895
                if (self::getVerbosityLevel() < $verbosityLevel) return false;
896
 
897
                if ($move_to_font) {
898
                        array_unshift($this->performanceDataObjects, $prefDataObj);
899
                } else {
900
                        $this->performanceDataObjects[] = $prefDataObj;
901
                }
902
 
903
                return true;
904
        }
905
 
906
        public function getPerformanceData() {
907
                $this->checkInitialized(); // if (!$this->initialized) return null;
908
 
909
                // see https://nagios-plugins.org/doc/guidelines.html#AEN200
910
                // 1. space separated list of label/value pairs
911
                return $this->performanceDataObjects;
912
        }
913
 
914
        public function removePerformanceData($prefDataObj) {
915
                if (($key = array_search($prefDataObj, $this->performanceDataObjects, true)) !== false) {
916
                        unset($this->performanceDataObjects[$key]);
917
                        return true;
918
                } else {
919
                        return false;
920
                }
921
        }
922
 
923
        public function clearPerformanceData() {
924
                $this->performanceDataObjects = array();
925
        }
926
 
927
        public function getVerboseInfo() {
928
                return $this->verbose_info;
929
        }
930
 
931
        public function clearVerboseInfo() {
932
                $this->verbose_info = '';
933
        }
934
 
935
        private function writeVisualHTML($special_content=null) {
936
                if (!_empty($special_content)) {
937
                        $content = $special_content;
938
                } else {
939
                        $content = strtoupper(VNagLang::$status.': '.VNagLang::status($this->status, $this->statusmodel))."\n\n";
940
 
941
                        $content .= strtoupper(VNagLang::$message).":\n";
942
                        $status_text = VNagLang::status($this->status, $this->statusmodel);
943
                        if (_empty($this->getHeadline())) {
944
                                $content .= $status_text;
945
                        } else {
946
                                if ($this->show_status_in_headline) {
947
                                        $content .= $status_text.': '.trim($this->getHeadline());
948
                                } else {
949
                                        $content .= trim($this->getHeadline());
950
                                }
951
                        }
952
                        $content .= "\n\n";
953
 
954
                        if (!_empty($this->verbose_info)) {
955
                                $content .= strtoupper(VNagLang::$verbose_info).":\n".trim($this->verbose_info)."\n\n";
956
                        }
957
 
958
                        $perfdata = $this->getPerformanceData();
959
                        if (count($perfdata) > 0) {
960
                                $content .= strtoupper(VNagLang::$performance_data).":\n";
961
                                foreach ($perfdata as $pd) {
962
                                        $content .= trim($pd)."\n";
963
                                }
964
                                $content .= "\n";
965
                        }
966
                }
967
 
968
                $colorinfo = '';
969
                $status = $this->getStatus();
970
 
971
                if ($status == VNag::STATUS_OK)                 $colorinfo = ' style="background-color:green;color:white;font-weight:bold"';
972
                else if ($status == VNag::STATUS_WARNING)       $colorinfo = ' style="background-color:yellow;color:black;font-weight:bold"';
973
                else if ($status == VNag::STATUS_CRITICAL)      $colorinfo = ' style="background-color:red;color:white;font-weight:bold"';
974
                else if ($status == VNag::STATUS_ERROR)         $colorinfo = ' style="background-color:purple;color:white;font-weight:bold"';
975
                else /* if ($status == VNag::STATUS_UNKNOWN) */ $colorinfo = ' style="background-color:lightgray;color:black;font-weight:bold"';
976
 
977
                $html_content = trim($content);
978
                $html_content = htmlentities($html_content);
979
                $html_content = str_replace(' ', '&nbsp;', $html_content);
980
                $html_content = nl2br($html_content);
981
 
982
                // FUT: Allow individual design via CSS
983
                return '<table border="1" cellspacing="2" cellpadding="2" style="width:100%" class="vnag_table">'.
984
                        '<tr'.$colorinfo.' class="vnag_title_row">'.
985
                        '<td>'.VNagLang::$nagios_output.'</td>'.
986
                        '</tr>'.
987
                        '<tr class="vnag_message_row">'.
988
                        '<td><code>'.
989
                        $html_content.
990
                        '</code></td>'.
991
                        '</tr>'.
992
                        '</table>';
993
        }
994
 
995
        protected function readInvisibleHTML($html) {
996
                $this->checkInitialized(); // if (!$this->initialized) return;
997
 
998
                $doc = new DOMDocument(); // Requires: aptitude install php-dom
999
                @$doc->loadHTML($html);   // added '@' because we don't want a warning for the non-standard <vnag> tag
1000
 
1001
                $tags = $doc->getElementsByTagName('script');
1002
                foreach ($tags as $tag) {
1003
                        $type = $tag->getAttribute('type');
1004
                        if ($type !== 'application/json') continue;
1005
 
1006
                        $json = $tag->nodeValue;
1007
                        if (!$json) continue;
1008
 
1009
                        $data = @json_decode($json,true);
1010
                        if (!is_array($data)) continue;
1011
 
1012
                        if (!isset($data['type'])) continue;
1013
                        if ($data['type'] === VNAG_JSONDATA_V1) {
1014
                                if (!isset($data['datasets'])) throw new VNagWebInfoException(VNagLang::$dataset_missing);
1015
                                foreach ($data['datasets'] as $dataset) {
1016
                                        $payload = base64_decode($dataset['payload']);
1017
                                        if (!$payload) {
1018
                                                throw new VNagWebInfoException(VNagLang::$payload_not_base64);
1019
                                        }
1020
 
1021
                                        if (isset($dataset['encryption'])) {
1022
                                                // The dataset is encrypted. We need to decrypt it first.
1023
 
1024
                                                $cryptInfo = $dataset['encryption'];
1025
                                                if (!is_array($cryptInfo)) {
1026
                                                        throw new VNagWebInfoException(VNagLang::$dataset_encryption_no_array);
1027
                                                }
1028
 
1029
                                                $password = is_null($this->password_in) ? '' : $this->password_in;
1030
 
1031
                                                $salt = base64_decode($cryptInfo['salt']);
1032
 
1033
                                                if ($cryptInfo['hash'] != hash('sha256',$salt.$password)) {
1034
                                                        if ($password == '') {
1035
                                                                throw new VNagWebInfoException(VNagLang::$require_password);
1036
                                                        } else {
1037
                                                                throw new VNagWebInfoException(VNagLang::$wrong_password);
1038
                                                        }
1039
                                                }
1040
 
21 daniel-mar 1041
                                                if (!function_exists('openssl_decrypt')) {
1042
                                                        throw new VNagException(VNagLang::$openssl_missing);
1043
                                                }
1044
 
2 daniel-mar 1045
                                                $payload = openssl_decrypt($payload, $cryptInfo['method'], $password, 0, $cryptInfo['iv']);
1046
                                        }
1047
 
1048
                                        if (!is_null($this->pubkey)) {
1049
                                                if (!file_exists($this->pubkey)) {
1050
                                                        throw new VNagInvalidArgumentException(sprintf(VNagLang::$pubkey_file_not_found, $this->pubkey));
1051
                                                }
1052
 
1053
                                                $public_key = @file_get_contents($this->pubkey);
1054
                                                if (!$public_key) {
1055
                                                        throw new VNagPublicKeyException(sprintf(VNagLang::$pubkey_file_not_readable, $this->pubkey));
1056
                                                }
1057
 
1058
                                                if (!isset($dataset['signature'])) {
1059
                                                        throw new VNagSignatureException(VNagLang::$signature_missing);
1060
                                                }
1061
 
1062
                                                $signature = base64_decode($dataset['signature']);
1063
                                                if (!$signature) {
1064
                                                        throw new VNagSignatureException(VNagLang::$signature_not_bas64);
1065
                                                }
1066
 
21 daniel-mar 1067
                                                if (!function_exists('openssl_verify')) {
1068
                                                        throw new VNagException(VNagLang::$openssl_missing);
1069
                                                }
1070
 
1071
                                                $sign_algo = is_null($this->sign_algo) ? OPENSSL_ALGO_SHA256 : $this->sign_algo;
1072
                                                if (!openssl_verify($payload, $signature, $public_key, $sign_algo)) {
2 daniel-mar 1073
                                                        throw new VNagSignatureException(VNagLang::$signature_invalid);
1074
                                                }
1075
                                        }
1076
 
1077
                                        $payload = @json_decode($payload,true);
1078
                                        if (!$payload) {
1079
                                                throw new VNagWebInfoException(VNagLang::$payload_not_json);
1080
                                        }
1081
 
1082
                                        if ($payload['id'] == $this->id) {
1083
                                                return $payload;
1084
                                        }
1085
                                }
1086
                        }
1087
                }
1088
 
1089
                return null;
1090
        }
1091
 
1092
        private function getNextMonitorID($peek=false) {
1093
                $result = is_null($this->id) ? self::$http_serial_number : $this->id;
1094
 
1095
                if (!$peek) {
1096
                        $this->id = null; // use manual ID only once
1097
                        self::$http_serial_number++;
1098
                }
1099
 
1100
                return $result;
1101
        }
1102
 
1103
        private function writeInvisibleHTML($special_content=null) {
1104
                // 1. Create the payload
1105
 
1106
                $payload['id'] = $this->getNextMonitorID();
1107
 
1108
                $payload['status'] = $this->getStatus();
1109
 
1110
                if (!_empty($special_content)) {
1111
                        $payload['text'] = $special_content;
1112
                } else {
1113
                        $payload['headline'] = $this->getHeadline();
1114
                        $payload['verbose_info'] = $this->verbose_info;
1115
 
1116
                        $payload['performance_data'] = array();
1117
                        foreach ($this->performanceDataObjects as $perfdata) {
1118
                                $payload['performance_data'][] = (string)$perfdata;
1119
                        }
1120
                }
1121
 
1122
                $payload = json_encode($payload);
1123
 
1124
                // 2. Encode the payload as JSON and optionally sign and/or encrypt it
1125
 
1126
                $dataset = array();
1127
 
1128
                if (!is_null($this->privkey)) {
21 daniel-mar 1129
                        if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {
1130
                                throw new VNagException(VNagLang::$openssl_missing);
1131
                        }
1132
 
2 daniel-mar 1133
                        if (!file_exists($this->privkey)) {
1134
                                throw new VNagInvalidArgumentException(sprintf(VNagLang::$privkey_file_not_found, $this->privkey));
1135
                        }
22 daniel-mar 1136
                        $pkeyid = @openssl_pkey_get_private('file://'.$this->privkey, $this->privkey_password);
2 daniel-mar 1137
                        if (!$pkeyid) {
1138
                                throw new VNagPrivateKeyException(sprintf(VNagLang::$privkey_file_not_readable, $this->privkey));
1139
                        }
1140
 
22 daniel-mar 1141
                        $signature = '';
19 daniel-mar 1142
                        if (@openssl_sign($payload, $signature, $pkeyid, $this->sign_algo)) {
1143
                                openssl_free_key($pkeyid);
2 daniel-mar 1144
 
19 daniel-mar 1145
                                $dataset['signature'] = base64_encode($signature);
1146
                        }
2 daniel-mar 1147
                }
1148
 
1149
                if (!is_null($this->password_out)) {
21 daniel-mar 1150
                        if (!function_exists('openssl_encrypt')) {
1151
                                throw new VNagException(VNagLang::$openssl_missing);
1152
                        }
1153
 
2 daniel-mar 1154
                        $password = $this->password_out;
1155
 
1156
                        $method = 'aes-256-ofb';
1157
                        $iv = substr(hash('sha256', openssl_random_pseudo_bytes(32)), 0, 16);
1158
                        $salt = openssl_random_pseudo_bytes(32);
1159
 
1160
                        $cryptInfo = array();
1161
                        $cryptInfo['method'] = $method;
1162
                        $cryptInfo['iv'] = $iv;
1163
                        $cryptInfo['salt'] = base64_encode($salt);
1164
                        $cryptInfo['hash'] = hash('sha256', $salt.$password);
1165
 
1166
                        $payload = openssl_encrypt($payload, $method, $password, 0, $iv);
1167
                        $dataset['encryption'] = $cryptInfo;
1168
                }
1169
 
1170
                $dataset['payload'] = base64_encode($payload);
1171
 
1172
                // 3. Encode everything as JSON+Base64 (again) and put it into the data block
1173
 
1174
                $json = array();
1175
                $json['type'] = VNAG_JSONDATA_V1;
1176
                $json['datasets'] = array($dataset); // we only output 1 dataset. We could technically output more than one into this data block.
1177
 
21 daniel-mar 1178
                // Include the machine-readable information as data block
2 daniel-mar 1179
                // This method was chosen to support HTML 4.01, XHTML and HTML5 as well without breaking the standards
1180
                // see https://stackoverflow.com/questions/51222713/using-an-individual-tag-without-breaking-the-standards/51223609#51223609
1181
                return '<script type="application/json">'.
1182
                       json_encode($json).
1183
                       '</script>';
1184
        }
1185
 
1186
        protected function appendHeadline($msg) {
1187
                $this->checkInitialized(); // if (!$this->initialized) return;
1188
 
1189
                if (_empty($msg)) return false;
1190
                $this->messages[] = $msg;
1191
 
1192
                return true;
1193
        }
1194
 
1195
        protected function changeHeadline($msg) {
1196
                $this->checkInitialized(); // if (!$this->initialized) return;
1197
 
1198
                if (_empty($msg)) {
1199
                        $this->messages = array();
1200
                } else {
1201
                        $this->messages = array($msg);
1202
                }
1203
 
1204
                return true;
1205
        }
1206
 
1207
        public function setHeadline($msg, $append=false, $verbosityLevel=VNag::VERBOSITY_SUMMARY) {
1208
                $this->checkInitialized(); // if (!$this->initialized) return;
1209
 
1210
                if ((!isset($this->argVerbosity)) && ($verbosityLevel > VNag::VERBOSITY_SUMMARY)) throw new VNagRequiredArgumentNotRegistered('-v');
1211
                if (self::getVerbosityLevel() < $verbosityLevel) $msg = '';
1212
 
1213
                if ($append) {
1214
                        return $this->appendHeadline($msg);
1215
                } else {
1216
                        return $this->changeHeadline($msg);
1217
                }
1218
        }
1219
 
1220
        public function getHeadline() {
1221
                $this->checkInitialized(); // if (!$this->initialized) return '';
1222
 
1223
                return implode(', ', $this->messages);
1224
        }
1225
 
1226
        public function addVerboseMessage($msg, $verbosityLevel=VNag::VERBOSITY_SUMMARY) {
1227
                $this->checkInitialized(); // if (!$this->initialized) return;
1228
 
1229
                if (self::getVerbosityLevel() >= $verbosityLevel) {
1230
                        $this->verbose_info .= $msg."\n";
1231
                }
1232
        }
1233
 
1234
        public function setStatus($status, $force=false) {
1235
                $this->checkInitialized(); // if (!$this->initialized) return;
1236
 
1237
                if (($force) || is_null($this->status) || ($status > $this->status)) {
1238
                        $this->status = $status;
1239
                }
1240
        }
1241
 
1242
        public function getStatus() {
1243
                $this->checkInitialized(); // if (!$this->initialized) return;
1244
 
1245
                return $this->status;
1246
        }
1247
 
1248
        protected static function exceptionText($exception) {
1249
                // $this->checkInitialized(); // if (!$this->initialized) return false;
1250
 
1251
                $class = get_class($exception);
1252
                $msg = $exception->getMessage();
1253
 
1254
                if (!_empty($msg)) {
1255
                        return sprintf(VNagLang::$exception_x, $msg, $class);
1256
                } else {
1257
                        return sprintf(VNagLang::$unhandled_exception_without_msg, $class);
1258
                }
1259
        }
1260
 
1261
        protected function handleException($exception) {
1262
                $this->checkInitialized(); // if (!$this->initialized) return;
1263
 
19 daniel-mar 1264
                if (!VNag::is_http_mode()) {
1265
                        // On console output, remove anything we have written so far!
1266
                        while (ob_get_level() > 0) @ob_end_clean();
1267
                }
2 daniel-mar 1268
                $this->clearVerboseInfo();
1269
                $this->clearPerformanceData();
1270
 
1271
                if ($exception instanceof VNagException) {
1272
                        $this->setStatus($exception->getStatus());
1273
                } else {
1274
                        $this->setStatus(self::STATUS_ERROR);
1275
                }
1276
 
1277
                $this->setHeadline($this->exceptionText($exception), false);
1278
 
22 daniel-mar 1279
                if ($exception instanceof VNagImplementationErrorException) {
2 daniel-mar 1280
                        $this->addVerboseMessage($exception->getTraceAsString(), VNag::VERBOSITY_SUMMARY);
1281
                } else {
1282
                        if (isset($this->argVerbosity)) {
1283
                                $this->addVerboseMessage($exception->getTraceAsString(), VNag::VERBOSITY_ADDITIONAL_INFORMATION);
1284
                        } else {
1285
                                // $this->addVerboseMessage($exception->getTraceAsString(), VNag::VERBOSITY_SUMMARY);
1286
                        }
1287
                }
1288
        }
1289
}
1290
 
1291
 
1292
class VNagException extends Exception {
1293
        public function getStatus() {
1294
                return VNag::STATUS_ERROR;
1295
        }
1296
}
1297
 
1298
class VNagTimeoutException extends VNagException {}
1299
 
1300
class VNagWebInfoException extends VNagException {}
1301
class VNagSignatureException extends VNagException {}
1302
class VNagPublicKeyException extends VNagException {}
1303
class VNagPrivateKeyException extends VNagException {}
1304
 
1305
// VNagInvalidArgumentException are exceptions which result from a wrong use
1306
// of arguments by the USER (CLI arguments or HTTP parameters)
1307
class VNagInvalidArgumentException extends VNagException {
1308
        public function getStatus() {
1309
                return VNag::STATUS_UNKNOWN;
1310
        }
1311
}
1312
 
1313
class VNagValueUomPairSyntaxException extends VNagInvalidArgumentException {
1314
        public function __construct($str) {
1315
                $e_msg = sprintf(VNagLang::$valueUomPairSyntaxError, $str);
1316
                parent::__construct($e_msg);
1317
        }
1318
}
1319
 
1320
class VNagInvalidTimeoutException extends VNagInvalidArgumentException {}
1321
 
1322
class VNagInvalidRangeException extends VNagInvalidArgumentException {
1323
        public function __construct($msg) {
1324
                $e_msg = VNagLang::$range_is_invalid;
1325
                if (!_empty($msg)) $e_msg .= ': '.trim($msg);
1326
                parent::__construct($e_msg);
1327
        }
1328
}
1329
 
1330
class VNagInvalidShortOpt extends VNagImplementationErrorException {}
1331
class VNagInvalidLongOpt extends VNagImplementationErrorException {}
1332
class VNagInvalidValuePolicy extends VNagImplementationErrorException {}
1333
class VNagIllegalStatusModel extends VNagImplementationErrorException {}
1334
class VNagNotConstructed extends VNagImplementationErrorException {}
1335
 
1336
// To enforce that people use the API correctly, we report flaws in the implementation
1337
// as Exception.
1338
class VNagImplementationErrorException extends VNagException {}
1339
 
1340
class VNagInvalidStandardArgument extends VNagImplementationErrorException  {}
1341
class VNagFunctionCallOutsideSession extends VNagImplementationErrorException {}
1342
class VNagIllegalArgumentValuesException extends VNagImplementationErrorException {}
1343
 
1344
class VNagRequiredArgumentNotRegistered extends VNagImplementationErrorException {
1345
        // Developer's mistake: The argument is not in the list of expected arguments
1346
        public function __construct($required_argument) {
1347
                $e_msg = sprintf(VNagLang::$query_without_expected_argument, $required_argument);
1348
                parent::__construct($e_msg);
1349
        }
1350
}
1351
 
1352
class VNagRequiredArgumentMissing extends VNagInvalidArgumentException {
1353
        // User's mistake: They did not pass the argument to the plugin
1354
        public function __construct($required_argument) {
1355
                $e_msg = sprintf(VNagLang::$required_argument_missing, $required_argument);
1356
                parent::__construct($e_msg);
1357
        }
1358
}
1359
 
1360
class VNagUnknownUomException extends VNagInvalidArgumentException {
1361
        public function __construct($uom) {
1362
                $e_msg = sprintf(VNagLang::$perfdata_uom_not_recognized, $uom);
1363
                parent::__construct($e_msg);
1364
        }
1365
}
1366
 
1367
class VNagNoCompatibleRangeUomFoundException extends VNagException {}
1368
 
1369
class VNagMixedUomsNotImplemented extends VNagInvalidArgumentException {
1370
        public function __construct($uom1, $uom2) {
1371
                if (_empty($uom1)) $uom1 = VNagLang::$none;
1372
                if (_empty($uom2)) $uom2 = VNagLang::$none;
1373
                $e_msg = sprintf(VNagLang::$perfdata_mixed_uom_not_implemented, $uom1, $uom2);
1374
                parent::__construct($e_msg);
1375
        }
1376
}
1377
 
1378
class VNagUomConvertException extends VNagInvalidArgumentException {
1379
        // It is unknown where the invalid UOM that was passed to the normalize() function came from,
1380
        // so it is not clear what parent this Exception class should have...
1381
        // If the value comes from the developer: VNagImplementationErrorException
1382
        // If the value came from the user: VNagInvalidArgumentException
1383
 
1384
        public function __construct($uom1, $uom2) {
1385
                if (_empty($uom1)) $uom1 = VNagLang::$none;
1386
                if (_empty($uom2)) $uom2 = VNagLang::$none;
1387
                $e_msg = sprintf(VNagLang::$convert_x_y_error, $uom1, $uom2);
1388
                parent::__construct($e_msg);
1389
        }
1390
}
1391
 
1392
class VNagInvalidPerformanceDataException extends VNagInvalidArgumentException {
1393
        public function __construct($msg) {
1394
                $e_msg = VNagLang::$performance_data_invalid;
1395
                if (!_empty($msg)) $e_msg .= ': '.trim($msg);
1396
                parent::__construct($e_msg);
1397
        }
1398
}
1399
 
1400
class Timeouter {
1401
        // based on http://stackoverflow.com/questions/7493676/detecting-a-timeout-for-a-block-of-code-in-php
1402
 
1403
        private static $start_time = false;
1404
        private static $timeout;
1405
        private static $fired      = false;
1406
        private static $registered = false;
1407
 
1408
        private function __construct() {
1409
        }
1410
 
1411
        public static function start($timeout) {
1412
                if (!is_numeric($timeout) || ($timeout <= 0)) {
1413
                        throw new VNagInvalidTimeoutException(sprintf(VNagLang::$timeout_value_invalid, $timeout));
1414
                }
1415
 
1416
                self::$start_time = microtime(true);
1417
                self::$timeout    = (float) $timeout;
1418
                self::$fired      = false;
1419
                if (!self::$registered) {
1420
                        self::$registered = true;
1421
                        register_tick_function(array('Timeouter', 'tick'));
1422
                }
1423
        }
1424
 
1425
        public static function started() {
1426
                return self::$registered;
1427
        }
1428
 
1429
        public static function end() {
1430
                if (self::$registered) {
1431
                        unregister_tick_function(array('Timeouter', 'tick'));
1432
                        self::$registered = false;
1433
                }
1434
        }
1435
 
1436
        public static function tick() {
1437
                if ((!self::$fired) && ((microtime(true) - self::$start_time) > self::$timeout)) {
1438
                        self::$fired = true; // do not fire again
1439
                        throw new VNagTimeoutException(VNagLang::$timeout_exception);
1440
                }
1441
        }
1442
}
1443
 
1444
class VNagArgument {
1445
        const VALUE_FORBIDDEN = 0;
1446
        const VALUE_REQUIRED  = 1;
1447
        const VALUE_OPTIONAL  = 2;
1448
 
1449
        protected $shortopt;
1450
        protected $longopts;
1451
        protected $valuePolicy;
1452
        protected $valueName;
1453
        protected $helpText;
1454
        protected $defaultValue = null;
1455
 
1456
        protected static $all_short = '';
1457
        protected static $all_long = array();
1458
 
1459
        public function getShortOpt() {
1460
                return $this->shortopt;
1461
        }
1462
 
1463
        public function getLongOpts() {
1464
                return $this->longopts;
1465
        }
1466
 
1467
        public function getValuePolicy() {
1468
                return $this->valuePolicy;
1469
        }
1470
 
1471
        public function getValueName() {
1472
                return $this->valueName;
1473
        }
1474
 
1475
        public function getHelpText() {
1476
                return $this->helpText;
1477
        }
1478
 
1479
        static private function validateShortOpt($shortopt) {
22 daniel-mar 1480
                $m = array();
2 daniel-mar 1481
                return preg_match('@^[a-zA-Z0-9\\+\\-\\?]$@', $shortopt, $m);
1482
        }
1483
 
1484
        static private function validateLongOpt($longopt) {
1485
                // FUT: Check if this is accurate
22 daniel-mar 1486
                $m = array();
2 daniel-mar 1487
                return preg_match('@^[a-zA-Z0-9\\+\\-\\?]+$@', $longopt, $m);
1488
        }
1489
 
1490
        // Note: Currently, we do not support following:
1491
        // 1. How many times may a value be defined (it needs to be manually described in $helpText)
1492
        // 2. Is this argument mandatory? (No exception will be thrown if the plugin will be started without this argument)
1493
        public function __construct($shortopt, $longopts, $valuePolicy, $valueName, $helpText, $defaultValue=null) {
1494
                // Check if $valueName is defined correctly in regards to the policy $valuePolicy
1495
                switch ($valuePolicy) {
1496
                        case VNagArgument::VALUE_FORBIDDEN:
1497
                                if (!_empty($valueName)) {
1498
                                        throw new VNagImplementationErrorException(sprintf(VNagLang::$value_name_forbidden));
1499
                                }
1500
                                break;
1501
                        case VNagArgument::VALUE_REQUIRED:
1502
                                if (_empty($valueName)) {
1503
                                        throw new VNagImplementationErrorException(sprintf(VNagLang::$value_name_required));
1504
                                }
1505
                                break;
1506
                        case VNagArgument::VALUE_OPTIONAL:
1507
                                if (_empty($valueName)) {
1508
                                        throw new VNagImplementationErrorException(sprintf(VNagLang::$value_name_required));
1509
                                }
1510
                                break;
1511
                        default:
1512
                                throw new VNagInvalidValuePolicy(sprintf(VNagLang::$illegal_valuepolicy, $valuePolicy));
1513
                }
1514
 
1515
                // We'll check: Does the shortopt contain illegal characters?
1516
                // http://stackoverflow.com/questions/28522387/which-chars-are-valid-shortopts-for-gnu-getopt
1517
                // We do not filter +, - and ?, since we might need it for other methods, e.g. VNagArgumentHandler::_addExpectedArgument
1518
                if (!_empty($shortopt)) {
1519
                        if (!self::validateShortOpt($shortopt)) {
1520
                                throw new VNagInvalidShortOpt(sprintf(VNagLang::$illegal_shortopt, $shortopt));
1521
                        }
1522
                }
1523
 
1524
                if (is_array($longopts)) { // $longopts is an array
1525
                        if (!is_null($longopts)) {
1526
                                foreach ($longopts as $longopt) {
1527
                                        if (!self::validateLongOpt($longopt)) {
1528
                                                throw new VNagInvalidLongOpt(sprintf(VNagLang::$illegal_longopt, $longopt));
1529
                                        }
1530
                                }
1531
                        }
1532
                } else if (!_empty($longopts)) { // $longopts is a string
1533
                        if (!self::validateLongOpt($longopts)) {
1534
                                throw new VNagInvalidLongOpt(sprintf(VNagLang::$illegal_longopt, $longopts));
1535
                        }
1536
                        $longopts = array($longopts);
1537
                } else {
1538
                        $longopts = array();
1539
                }
1540
 
1541
                # valuePolicy must be between 0..2 and being int
1542
                switch ($valuePolicy) {
1543
                        case VNagArgument::VALUE_FORBIDDEN:
1544
                                $policyApdx = '';
1545
                                break;
1546
                        case VNagArgument::VALUE_REQUIRED:
1547
                                $policyApdx = ':';
1548
                                break;
1549
                        case VNagArgument::VALUE_OPTIONAL:
1550
                                $policyApdx = '::';
1551
                                break;
1552
                        default:
1553
                                throw new VNagInvalidValuePolicy(sprintf(VNagLang::$illegal_valuepolicy, $valuePolicy));
1554
                }
1555
 
1556
                if ((!is_null($shortopt)) && ($shortopt != '?')) self::$all_short .= $shortopt.$policyApdx;
1557
                if (!is_null($longopts)) {
1558
                        foreach ($longopts as $longopt) {
1559
                                self::$all_long[] = $longopt.$policyApdx;
1560
                        }
1561
                }
1562
 
1563
                $this->shortopt     = $shortopt;
1564
                $this->longopts     = $longopts;
1565
                $this->valuePolicy  = $valuePolicy;
1566
                $this->valueName    = $valueName;
1567
                $this->helpText     = $helpText;
1568
                $this->defaultValue = $defaultValue;
1569
        }
1570
 
1571
        protected static function getOptions() {
1572
                // Attention: In PHP 5.6.19-0+deb8u1 (cli), $_REQUEST is always set, so we need is_http_mode() instead of isset($_REQUEST)!
1573
                global $OVERWRITE_ARGUMENTS;
1574
 
1575
                if (!is_null($OVERWRITE_ARGUMENTS)) {
1576
                        return $OVERWRITE_ARGUMENTS;
1577
                } else if (VNag::is_http_mode()) {
1578
                        return $_REQUEST;
1579
                } else {
1580
                        return getopt(self::$all_short, self::$all_long);
1581
                }
1582
        }
1583
 
1584
        public function count() {
1585
                $options = self::getOptions();
1586
 
1587
                $count = 0;
1588
 
1589
                if (isset($options[$this->shortopt])) {
1590
                        if (is_array($options[$this->shortopt])) {
1591
                                // e.g. -vvv
1592
                                $count += count($options[$this->shortopt]);
1593
                        } else {
1594
                                // e.g. -v
1595
                                $count += 1;
1596
                        }
1597
                }
1598
 
1599
                if (!is_null($this->longopts)) {
1600
                        foreach ($this->longopts as $longopt) {
1601
                                if (isset($options[$longopt])) {
1602
                                        if (is_array($options[$longopt])) {
1603
                                                // e.g. --verbose --verbose --verbose
1604
                                                $count += count($options[$longopt]);
1605
                                        } else {
1606
                                                // e.g. --verbose
1607
                                                $count += 1;
1608
                                        }
1609
                                }
1610
                        }
1611
                }
1612
 
1613
                return $count;
1614
        }
1615
 
1616
        public function available() {
1617
                $options = self::getOptions();
1618
 
1619
                if (isset($options[$this->shortopt])) return true;
1620
                if (!is_null($this->longopts)) {
1621
                        foreach ($this->longopts as $longopt) {
1622
                                if (isset($options[$longopt])) return true;
1623
                        }
1624
                }
1625
                return false;
1626
        }
1627
 
1628
        public function require() {
1629
                if (!$this->available() && is_null($this->defaultValue)) {
1630
                        $opt = $this->shortopt;
1631
                        $opt = !_empty($opt) ? '-'.$opt : (isset($this->longopts[0]) ? '--'.$this->longopts[0] : '?');
1632
                        throw new VNagRequiredArgumentMissing($opt);
1633
                }
1634
        }
1635
 
1636
        public function getValue() {
1637
                $options = self::getOptions();
1638
 
1639
                if (isset($options[$this->shortopt])) {
1640
                        $x = $options[$this->shortopt];
1641
                        if (is_array($x) && (count($x) <= 1)) $options[$this->shortopt] = $options[$this->shortopt][0];
1642
                        return $options[$this->shortopt];
1643
                }
1644
 
1645
                if (!is_null($this->longopts)) {
1646
                        foreach ($this->longopts as $longopt) {
1647
                                if (isset($options[$longopt])) {
1648
                                        $x = $options[$longopt];
1649
                                        if (is_array($x) && (count($x) <= 1)) $options[$longopt] = $options[$longopt][0];
1650
                                        return $options[$longopt];
1651
                                }
1652
                        }
1653
                }
1654
 
1655
                return $this->defaultValue;
1656
        }
1657
}
1658
 
1659
class VNagArgumentHandler {
1660
        protected $expectedArgs = array();
1661
 
1662
        // Will be called by VNag via ReflectionMethod (like C++ style friends), because it should not be called manually.
1663
        // Use VNag's function instead (since it adds to the helpObj too)
1664
        protected function _addExpectedArgument($argObj) {
1665
                // -? is always illegal, so it will trigger illegalUsage(). So we don't add it to the list of
1666
                // expected arguments, otherwise illegalUsage() would be true.
1667
                if ($argObj->getShortOpt() == '?') return false;
1668
 
1669
                // GNU extensions with a special meaning
1670
                if ($argObj->getShortOpt() == '-') return false; // cancel parsing
1671
                if ($argObj->getShortOpt() == '+') return false; // enable POSIXLY_CORRECT
1672
 
1673
                $this->expectedArgs[] = $argObj;
1674
                return true;
1675
        }
1676
 
1677
        public function getArgumentObj($shortopt) {
1678
                foreach ($this->expectedArgs as $argObj) {
1679
                        if ($argObj->getShortOpt() == $shortopt) return $argObj;
1680
                }
1681
                return null;
1682
        }
1683
 
1684
        public function isArgRegistered($shortopt) {
1685
                return !is_null($this->getArgumentObj($shortopt));
1686
        }
1687
 
1688
        public function illegalUsage() {
1689
                // In this function, we should check if $argv (resp. getopts) contains stuff which is not expected or illegal,
1690
                // so the script can show an usage information and quit the program.
1691
 
1692
                // WONTFIX: PHP's horrible implementation of GNU's getopt does not allow following intended tasks:
1693
                // - check for illegal values/arguments (e.g. the argument -? which is always illegal)
1694
                // - check for missing values (e.g. -H instead of -H localhost )
1695
                // - check for unexpected arguments (e.g. -x if only -a -b -c are defined in $expectedArgs as expected arguments)
1696
                // - Of course, everything behind "--" may not be evaluated
1697
                // see also http://stackoverflow.com/questions/25388130/catch-unexpected-options-with-getopt
1698
 
1699
                // So the only way is to do this stupid hard coded check for '-?'
1700
                // PHP sucks...
1701
                global $argv;
1702
                return (isset($argv[1])) && (($argv[1] == '-?') || ($argv[1] == '/?'));
1703
        }
1704
}
1705
 
1706
class VNagRange {
1707
        // see https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
1708
        // We allow UOMs inside the range definition, e.g. "-w @10M:50M"
1709
 
1710
        public /*VNagValueUomPair|'inf'*/ $start;
1711
        public /*VNagValueUomPair|'inf'*/ $end;
1712
        public /*boolean*/ $warnInsideRange;
1713
 
1714
        public function __construct($rangeDef, $singleValueBehavior=VNag::SINGLEVALUE_RANGE_DEFAULT) {
22 daniel-mar 1715
                $m = array();
2 daniel-mar 1716
                //if (!preg_match('|(@){0,1}(\d+)(:){0,1}(\d+){0,1}|', $rangeDef, $m)) {
1717
                if (!preg_match('|^(@){0,1}([^:]+)(:){0,1}(.*)$|', $rangeDef, $m)) {
1718
                        throw new VNagInvalidRangeException(sprintf(VNagLang::$range_invalid_syntax, $rangeDef));
1719
                }
1720
 
1721
                $this->warnInsideRange = $m[1] === '@';
1722
 
1723
                $this->start = null;
1724
                $this->end   = null;
1725
 
1726
                if ($m[3] === ':') {
1727
                        if ($m[2] === '~') {
1728
                                $this->start = 'inf';
1729
                        } else {
1730
                                $this->start = new VNagValueUomPair($m[2]);
1731
                        }
1732
 
1733
                        if (_empty($m[4])) {
1734
                                $this->end = 'inf';
1735
                        } else {
1736
                                $this->end = new VNagValueUomPair($m[4]);
1737
                        }
1738
                } else {
1739
                        assert(_empty($m[4]));
1740
                        assert(!_empty($m[2]));
1741
 
1742
                        $x = $m[2];
1743
 
1744
                        if ($singleValueBehavior == VNag::SINGLEVALUE_RANGE_DEFAULT) {
1745
                                // Default behavior according to the development guidelines:
1746
                                //  x means  x:x, which means, everything except x% is bad.
1747
                                // @x means @x:x, which means, x is bad and everything else is good.
1748
                                $this->start = new VNagValueUomPair($x);
1749
                                $this->end   = new VNagValueUomPair($x);
1750
                        } else if ($singleValueBehavior == VNag::SINGLEVALUE_RANGE_VAL_GT_X_BAD) {
1751
                                // The single value x means, everything > x is bad. @x is not defined.
1752
                                if ($this->warnInsideRange) throw new VNagInvalidRangeException(VNagLang::$singlevalue_unexpected_at_symbol);
1753
                                $this->warnInsideRange = 0;
1754
                                $this->start = 'inf';
1755
                                $this->end   = new VNagValueUomPair($x);
1756
                        } else if ($singleValueBehavior == VNag::SINGLEVALUE_RANGE_VAL_GE_X_BAD) {
1757
                                // The single value x means, everything >= x is bad. @x is not defined.
1758
                                if ($this->warnInsideRange) throw new VNagInvalidRangeException(VNagLang::$singlevalue_unexpected_at_symbol);
1759
                                $this->warnInsideRange = 1;
1760
                                $this->start = new VNagValueUomPair($x);
1761
                                $this->end   = 'inf';
1762
                        } else if ($singleValueBehavior == VNag::SINGLEVALUE_RANGE_VAL_LT_X_BAD) {
1763
                                // The single value x means, everything < x is bad. @x is not defined.
1764
                                if ($this->warnInsideRange) throw new VNagInvalidRangeException(VNagLang::$singlevalue_unexpected_at_symbol);
1765
                                $this->warnInsideRange = 0;
1766
                                $this->start = new VNagValueUomPair($x);
1767
                                $this->end   = 'inf';
1768
                        } else if ($singleValueBehavior == VNag::SINGLEVALUE_RANGE_VAL_LE_X_BAD) {
1769
                                // The single value x means, everything <= x is bad. @x is not defined.
1770
                                if ($this->warnInsideRange) throw new VNagInvalidRangeException(VNagLang::$singlevalue_unexpected_at_symbol);
1771
                                $this->warnInsideRange = 1;
1772
                                $this->start = 'inf';
1773
                                $this->end   = new VNagValueUomPair($x);
1774
                        } else {
1775
                                throw new VNagException(VNagLang::$illegalSingleValueBehavior);
1776
                        }
1777
                }
1778
 
1779
                // Check if range is valid
1780
                if (is_null($this->start)) {
1781
                        throw new VNagInvalidRangeException(VNagLang::$invalid_start_value);
1782
                }
1783
                if (is_null($this->end)) {
1784
                        throw new VNagInvalidRangeException(VNagLang::$invalid_end_value);
1785
                }
1786
                if (($this->start instanceof VNagValueUomPair) && ($this->end instanceof VNagValueUomPair) &&
1787
                    (VNagValueUomPair::compare($this->start,$this->end) > 0)) {
1788
                        throw new VNagInvalidRangeException(VNagLang::$start_is_greater_than_end);
1789
                }
1790
        }
1791
 
1792
        public function __toString() {
1793
                // Attention:
1794
                // - this function assumes that $start and $end are valid.
1795
                // - not the shortest result will be chosen
1796
 
1797
                $ret = '';
1798
                if ($this->warnInsideRange) {
1799
                        $ret = '@';
1800
                }
1801
 
1802
                if ($this->start === 'inf') {
1803
                        $ret .= '~';
1804
                } else {
1805
                        $ret .= $this->start;
1806
                }
1807
 
1808
                $ret .= ':';
1809
 
1810
                if ($this->end !== 'inf') {
1811
                        $ret .= $this->end;
1812
                }
1813
 
1814
                return $ret;
1815
        }
1816
 
1817
        public function checkAlert($values) {
1818
                $compatibleCount = 0;
1819
 
1820
                if (!is_array($values)) $values = array($values);
1821
                foreach ($values as $value) {
1822
                        if (!($value instanceof VNagValueUomPair)) $value = new VNagValueUomPair($value);
1823
 
8 daniel-mar 1824
                        assert(($this->start === 'inf') || ($this->start instanceof VNagValueUomPair));
1825
                        assert(($this->end   === 'inf') || ($this->end   instanceof VNagValueUomPair));
2 daniel-mar 1826
 
1827
                        if (($this->start !== 'inf') && (!$this->start->compatibleWith($value))) continue;
1828
                        if (($this->end   !== 'inf') && (!$this->end->compatibleWith($value)))   continue;
1829
                        $compatibleCount++;
1830
 
1831
                        if ($this->warnInsideRange) {
1832
                                return (($this->start === 'inf') || (VNagValueUomPair::compare($value,$this->start) >= 0)) &&
1833
                                       (($this->end   === 'inf') || (VNagValueUomPair::compare($value,$this->end)   <= 0));
1834
                        } else {
1835
                                return (($this->start !== 'inf') && (VNagValueUomPair::compare($value,$this->start) <  0)) ||
1836
                                       (($this->end   !== 'inf') && (VNagValueUomPair::compare($value,$this->end)   >  0));
1837
                        }
1838
                }
1839
 
1840
                if ((count($values) > 0) and ($compatibleCount == 0)) {
1841
                        throw new VNagNoCompatibleRangeUomFoundException(VNagLang::$no_compatible_range_uom_found);
1842
                }
1843
 
1844
                return false;
1845
        }
1846
}
1847
 
1848
class VNagValueUomPair {
1849
        protected $value;
1850
        protected $uom;
1851
        public $roundTo = -1;
1852
 
1853
        public function isRelative() {
1854
                return $this->uom === '%';
1855
        }
1856
 
1857
        public function getValue() {
1858
                return $this->value;
1859
        }
1860
 
1861
        public function getUom() {
1862
                return $this->uom;
1863
        }
1864
 
1865
        public function __toString() {
1866
                if ($this->roundTo == -1) {
1867
                        return $this->value.$this->uom;
1868
                } else {
1869
                        return round($this->value,$this->roundTo).$this->uom;
1870
                }
1871
        }
1872
 
1873
        public function __construct($str) {
22 daniel-mar 1874
                $m = array();
2 daniel-mar 1875
                if (!preg_match('/^([\d\.]+)(.*)$/ism', $str, $m)) {
1876
                        throw new VNagValueUomPairSyntaxException($str);
1877
                }
1878
                $this->value = $m[1];
1879
                $this->uom = isset($m[2]) ? $m[2] : '';
1880
 
1881
                if (!self::isKnownUOM($this->uom)) {
1882
                        throw new VNagUnknownUomException($this->uom);
1883
                }
1884
        }
1885
 
1886
        public static function isKnownUOM($uom) {
1887
                // see https://nagios-plugins.org/doc/guidelines.html#AEN200
1888
                // 10. UOM (unit of measurement) is one of:
1889
                return (
1890
                                // no unit specified - assume a number (int or float) of things (eg, users, processes, load averages)
1891
                                ($uom === '') ||
1892
                                // s - seconds (also us, ms)
1893
                                ($uom === 's') || ($uom === 'ms') || ($uom === 'us') ||
1894
                                // % - percentage
1895
                                ($uom === '%') ||
1896
                                // B - bytes (also KB, MB, TB)
1897
                                ($uom === 'B') || ($uom === 'KB') || ($uom === 'MB') || ($uom === 'GB') || ($uom === 'TB') || // NOTE: GB is not in the official development guidelines,probably due to an error, so I've added them anyway
1898
                                // c - a continous counter (such as bytes transmitted on an interface)
1899
                                ($uom === 'c')
1900
                        );
1901
        }
1902
 
1903
        public function normalize($target=null) {
1904
                $res = clone $this;
1905
 
1906
                // The value is normalized to seconds or megabytes
1907
                if ($res->uom === 'ms') {
1908
                        $res->uom = 's';
1909
                        $res->value /= 1000;
1910
                }
1911
                if ($res->uom === 'us') {
1912
                        $res->uom = 's';
1913
                        $res->value /= 1000 * 1000;
1914
                }
1915
                if ($res->uom === 'B') {
1916
                        $res->uom = 'MB';
1917
                        $res->value /= 1024 * 1024;
1918
                }
1919
                if ($res->uom === 'KB') {
1920
                        $res->uom = 'MB';
1921
                        $res->value /= 1024;
1922
                }
1923
                if ($res->uom === 'GB') {
1924
                        $res->uom = 'MB';
1925
                        $res->value *= 1024;
1926
                }
1927
                if ($res->uom === 'TB') {
1928
                        $res->uom = 'MB';
1929
                        $res->value *= 1024 * 1024;
1930
                }
1931
                if ($res->uom === 'c') {
1932
                        $res->uom = '';
1933
                }
1934
 
1935
                // Now, if the user wishes, convert to another unit
1936
                if (!is_null($target)) {
1937
                        if ($res->uom == 'MB') {
1938
                                if ($target == 'B') {
1939
                                        $res->uom = 'B';
1940
                                        $res->value *= 1024 * 1024;
1941
                                } else if ($target == 'KB') {
1942
                                        $res->uom = 'KB';
1943
                                        $res->value *= 1024;
1944
                                } else if ($target == 'GB') {
1945
                                        $res->uom = 'GB';
1946
                                        $res->value /= 1024;
1947
                                } else if ($target == 'TB') {
1948
                                        $res->uom = 'TB';
1949
                                        $res->value /= 1024 * 1024;
1950
                                } else {
1951
                                        throw new VNagUomConvertException($res->uom, $target);
1952
                                }
1953
                        } else if ($res->uom == 's') {
1954
                                if ($target == 'ms') {
1955
                                        $res->uom = 'ms';
1956
                                        $res->value /= 1000;
1957
                                } else if ($target == 'us') {
1958
                                        $res->uom = 'us';
1959
                                        $res->value /= 1000 * 1000;
1960
                                } else {
1961
                                        throw new VNagUomConvertException($res->uom, $target);
1962
                                }
1963
                        } else {
1964
                                throw new VNagUomConvertException($res->uom, $target);
1965
                        }
1966
                }
1967
 
1968
                return $res;
1969
        }
1970
 
1971
        public function compatibleWith(VNagValueUomPair $other) {
1972
                $a = $this->normalize();
1973
                $b = $other->normalize();
1974
 
1975
                return ($a->uom == $b->uom);
1976
        }
1977
 
1978
        public static function compare(VNagValueUomPair $left, VNagValueUomPair $right) {
1979
                $a = $left->normalize();
1980
                $b = $right->normalize();
1981
 
1982
                // FUT: Also accept mixed UOMs, e.g. MB and %
1983
                //      To translate between an absolute and a relative value, the
1984
                //      reference value (100%=?) needs to be passed through this comparison
1985
                //      function somehow.
1986
                if ($a->uom != $b->uom) throw new VNagMixedUomsNotImplemented($a->uom, $b->uom);
1987
 
1988
                if ($a->value  > $b->value) return  1;
1989
                if ($a->value == $b->value) return  0;
1990
                if ($a->value  < $b->value) return -1;
1991
        }
1992
}
1993
 
1994
class VNagPerformanceData {
1995
        // see https://nagios-plugins.org/doc/guidelines.html#AEN200
1996
        //     https://www.icinga.com/docs/icinga1/latest/en/perfdata.html#formatperfdata
1997
 
1998
        protected $label;
1999
        protected /*VNagValueUomPair*/ $value;
2000
        protected $warn = null;
2001
        protected $crit = null;
2002
        protected $min = null;
2003
        protected $max = null;
2004
 
2005
        public static function createByString($perfdata) {
2006
                $perfdata = trim($perfdata);
2007
 
2008
                $ary = explode('=',$perfdata);
2009
                if (count($ary) != 2) {
2010
                        throw new VNagInvalidPerformanceDataException(sprintf(VNagLang::$perfdata_line_invalid, $perfdata));
2011
                }
2012
                $label = $ary[0];
2013
                $bry = explode(';',$ary[1]);
2014
                if (substr($label,0,1) === "'") $label = substr($label, 1, strlen($label)-2);
2015
                $value = $bry[0];
2016
                $warn  = isset($bry[1]) ? $bry[1] : null;
2017
                $crit  = isset($bry[2]) ? $bry[2] : null;
2018
                $min   = isset($bry[3]) ? $bry[3] : null;
2019
                $max   = isset($bry[4]) ? $bry[4] : null;
2020
 
2021
                // Guideline "7. min and max are not required if UOM=%" makes no sense, because
2022
                // actually, all fields (except label and value) are optional.
2023
 
2024
                return new self($label, $value, $warn, $crit, $min, $max);
2025
        }
2026
 
2027
        public function __construct($label, $value/*may include UOM*/, $warn=null, $crit=null, $min=null, $max=null) {
2028
                // Not checked / Nothing to check:
2029
                // - 4. label length is arbitrary, but ideally the first 19 characters are unique (due to a limitation in RRD). Be aware of a limitation in the amount of data that NRPE returns to Nagios
2030
                // - 6. warn, crit, min or max may be null (for example, if the threshold is not defined or min and max do not apply). Trailing unfilled semicolons can be dropped
2031
                // - 9. warn and crit are in the range format (see the Section called Threshold and ranges). Must be the same UOM
2032
                // - 7. min and max are not required if UOM=%
2033
 
2034
                // 2. label can contain any characters except the equals sign or single quote (')
2035
                if (strpos($label, '=') !== false) throw new VNagInvalidPerformanceDataException(VNagLang::$perfdata_label_equal_sign_forbidden);
2036
 
2037
                // 5. to specify a quote character, use two single quotes
2038
                $label = str_replace("'", "''", $label);
2039
 
2040
                // 8. value, min and max in class [-0-9.]. Must all be the same UOM.
2041
                //    value may be a literal "U" instead, this would indicate that the actual value couldn't be determined
2042
                /*
2043
                if (($value != 'U') && (!preg_match('|^[-0-9\\.]+$|', $value, $m))) {
2044
                        throw new VNagInvalidPerformanceDataException(VNagLang::$perfdata_value_must_be_in_class);
2045
                }
2046
                */
22 daniel-mar 2047
                $m = array();
2 daniel-mar 2048
                if ((!_empty($min)) && (!preg_match('|^[-0-9\\.]+$|', $min, $m))) {
2049
                        throw new VNagInvalidPerformanceDataException(VNagLang::$perfdata_min_must_be_in_class);
2050
                }
2051
                if ((!_empty($max)) && (!preg_match('|^[-0-9\\.]+$|', $max, $m))) {
2052
                        throw new VNagInvalidPerformanceDataException(VNagLang::$perfdata_max_must_be_in_class);
2053
                }
2054
 
2055
                // 10. UOM (unit of measurement) is one of ....
2056
                //     => This rule is checked in the VNagValueUomPair constructor.
2057
 
2058
                $this->label = $label;
2059
                $this->value = ($value == 'U') ? 'U' : new VNagValueUomPair($value);
2060
                $this->warn  = $warn;
2061
                $this->crit  = $crit;
2062
                $this->min   = $min;
2063
                $this->max   = $max;
2064
        }
2065
 
2066
        public function __toString() {
2067
                $label = $this->label;
2068
                $value = $this->value;
2069
                $warn  = $this->warn;
2070
                $crit  = $this->crit;
2071
                $min   = $this->min;
2072
                $max   = $this->max;
2073
 
2074
                // 5. to specify a quote character, use two single quotes
2075
                $label = str_replace("''", "'", $label);
2076
 
2077
                // 'label'=value[UOM];[warn];[crit];[min];[max]
2078
                // 3. the single quotes for the label are optional. Required if spaces are in the label
2079
                return "'$label'=$value".
2080
                       ';'.(is_null($warn) ? '' : $warn).
2081
                       ';'.(is_null($crit) ? '' : $crit).
2082
                       ';'.(is_null($min)  ? '' : $min).
2083
                       ';'.(is_null($max)  ? '' : $max);
2084
        }
2085
}
2086
 
2087
class VNagHelp {
2088
        public $word_wrap_width = 80; // -1 = disable
2089
        public $argument_indent = 7;
2090
 
2091
        public function printUsagePage() {
2092
                $usage = $this->getUsage();
2093
 
2094
                if (_empty($usage)) {
2095
                        $usage = VNagLang::$no_syntax_defined;
2096
                }
2097
 
2098
                return trim($usage)."\n";
2099
        }
2100
 
2101
        public function printVersionPage() {
2102
                $out = trim($this->getNameAndVersion())."\n";
2103
 
2104
                if ($this->word_wrap_width > 0) $out = wordwrap($out, $this->word_wrap_width, "\n", false);
2105
 
2106
                return $out;
2107
        }
2108
 
2109
        static private function _conditionalLine($line, $terminator='', $prefix='') {
2110
                if (!_empty($line)) {
2111
                        return trim($line).$terminator;
2112
                }
2113
                return '';
2114
        }
2115
 
2116
        public function printHelpPage() {
2117
                $out  = '';
2118
                $out .= self::_conditionalLine($this->getNameAndVersion(), "\n");
2119
                $out .= self::_conditionalLine($this->getCopyright(), "\n");
2120
                $out .= ($out != '') ? "\n" : '';
2121
                $out .= self::_conditionalLine($this->getShortDescription(), "\n\n\n");
2122
                $out .= self::_conditionalLine($this->getUsage(), "\n\n");
2123
 
2124
                $out .= VNagLang::$options."\n";
2125
                foreach ($this->options as $argObj) {
2126
                        $out .= $this->printArgumentHelp($argObj);
2127
                }
2128
 
2129
                $out .= self::_conditionalLine($this->getFootNotes(), "\n\n", "\n");
2130
 
2131
                if ($this->word_wrap_width > 0) $out = wordwrap($out, $this->word_wrap_width, "\n", false);
2132
 
2133
                return $out;
2134
        }
2135
 
2136
        protected /* VNagArgument[] */ $options = array();
2137
 
2138
        // Will be called by VNag via ReflectionMethod (like C++ style friends), because it should not be called manually.
2139
        // Use VNag's function instead (since it adds to the argHandler too)
2140
        protected function _addOption($argObj) {
2141
                $this->options[] = $argObj;
2142
        }
2143
 
2144
        # FUT: Automatic creation of usage page. Which arguments are necessary?
2145
        protected function printArgumentHelp($argObj) {
2146
                $identifiers = array();
2147
 
2148
                $shortopt = $argObj->getShortopt();
2149
                if (!_empty($shortopt)) $identifiers[] = '-'.$shortopt;
2150
 
2151
                $longopts = $argObj->getLongopts();
2152
                if (!is_null($longopts)) {
2153
                        foreach ($longopts as $longopt) {
2154
                                if (!_empty($longopt)) $identifiers[] = '--'.$longopt;
2155
                        }
2156
                }
2157
 
2158
                if (count($identifiers) == 0) return;
2159
 
2160
                $valueName = $argObj->getValueName();
2161
 
2162
                $arginfo = '';
2163
                switch ($argObj->getValuePolicy()) {
2164
                        case VNagArgument::VALUE_FORBIDDEN:
2165
                                $arginfo = '';
2166
                                break;
2167
                        case VNagArgument::VALUE_REQUIRED:
2168
                                $arginfo = '='.$valueName;
2169
                                break;
2170
                        case VNagArgument::VALUE_OPTIONAL:
2171
                                $arginfo = '[='.$valueName.']';
2172
                                break;
2173
                }
2174
 
2175
                $out = '';
2176
                $out .= implode(', ', $identifiers).$arginfo."\n";
2177
 
2178
                // https://nagios-plugins.org/doc/guidelines.html#AEN302 recommends supporting a 80x23 screen resolution.
2179
                // While we cannot guarantee the vertical height, we can limit the width at least...
2180
 
2181
                $content = trim($argObj->getHelpText());
2182
                if ($this->word_wrap_width > 0) $content = wordwrap($content, $this->word_wrap_width-$this->argument_indent, "\n", false);
2183
                $lines = explode("\n", $content);
2184
 
2185
                foreach ($lines as $line) {
2186
                        $out .= str_repeat(' ', $this->argument_indent).$line."\n";
2187
                }
2188
                $out .= "\n";
2189
 
2190
                return $out;
2191
        }
2192
 
2193
        // $pluginName should contain the name of the plugin, without version.
2194
        protected $pluginName;
2195
        public function setPluginName($pluginName) {
2196
                $this->pluginName = $this->replaceStuff($pluginName);
2197
        }
2198
        public function getPluginName() {
2199
                if (_empty($this->pluginName)) {
2200
                        global $argv;
2201
                        return basename($argv[0]);
2202
                } else {
2203
                        return $this->pluginName;
2204
                }
2205
        }
2206
 
2207
        private static function isCertified($file) {
2208
                $cont = file_get_contents($file);
2209
 
2210
                $regex = '@<\?php /\* <ViaThinkSoftSignature>(.+)</ViaThinkSoftSignature> \*/ \?>\n@ismU';
2211
 
22 daniel-mar 2212
                $m = array();
2 daniel-mar 2213
                if (!preg_match($regex, $cont, $m)) {
2214
                        return false;
2215
                }
2216
                $signature = base64_decode($m[1]);
2217
 
2218
                $naked = preg_replace($regex, '', $cont);
2219
                $hash = hash("sha256", $naked.basename($file));
2220
 
2221
                $public_key = <<<'VTSKEY'
2222
-----BEGIN PUBLIC KEY-----
2223
MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEA4UEmad2KHWzfGLcAzbOD
2224
IhqWyoPA1Cg4zN5YK/CWUiE7sh2CNinIwYqGnIOhZLp54/Iyv3H05QeWJU7kD+jQ
2225
5JwR8+pqk8ZGBfqlxXUBJ2bZhYIBJZYfilSROa7jgPPrrw0CjdGLmM3wmc8ztQRv
2226
4GpP7MaKVyVOsRz5xEcpzghWZ+Cl8Nxq1Vo02RkMYOOPA16abxZ65lVM8Vv2EKGf
2227
/VAVViTFvLWPxggvt1fbJJniC0cwt8gjzFXt6IJJSRlqc1lOO9ZIa/EWDKuHKQ1n
2228
ENQCqnuVPFDZU3lU20Z+6+EA0YngcvNYi3ucdIvgBd4Yv5FetzuxiOZUoDRfh/3R
2229
6dCJ8CvRiq0BSZcynTIWNmF3AVsH7vjxZe8kMDbwZNnR0suZ5MfBh6L/s1lCEWlS
2230
GwmCLc3MnOLxq3JLnfmbVa509YxlUamdSswnvzes28AjnzQ3LQchspP2a8bSXH6/
2231
qpbwvmV5WiNgwJck04VhaXrRRy3XFSwuk7KU/L4aqadXP26kgDqIYNvPXSa9JyGc
2232
14zwdmAtn36o8vpXM/A7GhdWqgPLlJbdTdK6IfwpBs8P/JB6y3t6RzAGiEOITdj9
2233
QUhW+sAoKno0j4WT7s80vWNWz37WoFJcvVLnVEYitnW6DqM+GOt2od3g6WgI6dOa
2234
MESA4J44Y4x1gXBw/M6F/ZngP4EJoAUG0GbzsaZ6HKLt4pDTZmw8PnNcXrOMYkr/
2235
N5EliTXil45DCaLkgNJmpdXjNpIvShW4ogq2osw+SQUalnAbW8ddiaOVCdgXkDFq
2236
gvnl5QSeUrKPF5v+vlnwWar6Rp7iInQpnA+PTSbAlO3Dd9WqbWx+uNoI/kXUlN0O
2237
a/vi5Uwat2Bz3N+jIpnBqg4+O+SG0z3UCVmT6Leg+kqO/rXbzoVv/DV7E30vTqdo
2238
wsswdJEM1BI7Wyid6HPwBek+rdv77gUg3W37vUcdfKxsYRcoHriXLHpmENznJcEx
2239
/nvilw6To1zx2LKmM/p56MQriKkXnqoOBpkpn3PaWyXZKY9xJNTAbcSP3haE7z9p
2240
PzJw88KI8dnYuFg4yS/AgmVGAUtu3bhDG4qF9URu2ck868zViH996lraYkmFIWJG
2241
r7h1LImhrwDEJvb/rOW8QvOZBX9H6pcSKs/LQbeoy6HMIOTlny+S15xtiS4t6Ayv
2242
3m0ry5c0qkl/mgKvGpeRnNlrcr6mb2fzxxGvcuBzi25wgIbRLPgJoqsmeBvW1OLU
2243
+9DpkNvitEJnPRo86v0VF86aou12Sm8Wb4mtrQ7h3qLIYvw2LN2mYh4WlgrSwPpx
2244
YvE2+vWapilnnDWoiu2ZmDWa7WW/ihqvX9fmp/qzxQvJmBYIN8dFpgcNLqSx526N
2245
bwIDAQAB
2246
-----END PUBLIC KEY-----
2247
VTSKEY;
2248
 
21 daniel-mar 2249
                if (!function_exists('openssl_verify')) return null;
2250
 
2 daniel-mar 2251
                if (!openssl_verify($hash, $signature, $public_key, OPENSSL_ALGO_SHA256)) {
2252
                        return false;
2253
                }
2254
 
2255
                return true;
2256
        }
2257
 
2258
        // $version should contain the version, not the program name or copyright.
2259
        protected $version;
2260
        public function setVersion($version) {
2261
                $this->version = $this->replaceStuff($version);
2262
        }
2263
        public function getVersion() {
2264
                return $this->version;
2265
        }
2266
        public function getNameAndVersion() {
2267
                $ret = $this->getPluginName();
2268
                if (_empty($ret)) return null;
2269
 
2270
                $ver = $this->getVersion();
2271
                if (!_empty($ver)) {
2272
                        $ret = sprintf(VNagLang::$x_version_x, $ret, $ver);
2273
                }
2274
                $ret = trim($ret);
2275
 
2276
                $certified = true;
2277
                foreach (get_included_files() as $file) {
2278
                        $certified &= self::isCertified($file);
2279
                }
2280
                if ($certified) {
2281
                        $ret .= ' (' . VNagLang::$certified . ')';
2282
                }
2283
 
2284
                return $ret;
2285
        }
2286
 
2287
        // $copyright should contain the copyright only, no program name or version.
2288
        // $CURYEAR$ will be replaced by the current year
2289
        protected $copyright;
2290
        public function setCopyright($copyright) {
2291
                $this->copyright = $this->replaceStuff($copyright);
2292
        }
2293
 
2294
        private function getVNagCopyright() {
2295
                if (VNag::is_http_mode()) {
2296
                        $vts_email = 'www.viathinksoft.com'; // don't publish email address at web services because of spam bots
2297
                } else {
2298
                        $vts_email = base64_decode('aW5mb0B2aWF0aGlua3NvZnQuZGU='); // protect email address from spambots which might parse this code
2299
                }
2300
                return "VNag Framework ".VNag::VNAG_VERSION." (C) 2014-".date('Y')." ViaThinkSoft <$vts_email>";
2301
        }
2302
 
2303
        public function getCopyright() {
2304
                if (_empty($this->copyright)) {
2305
                        return sprintf(VNagLang::$plugin_uses, $this->getVNagCopyright());
2306
                } else {
2307
                        return trim($this->copyright)."\n".sprintf(VNagLang::$uses, $this->getVNagCopyright());
2308
                }
2309
        }
2310
 
2311
        // $shortDescription should describe what this plugin does.
2312
        protected $shortDescription;
2313
        public function setShortDescription($shortDescription) {
2314
                $this->shortDescription = $this->replaceStuff($shortDescription);
2315
        }
2316
        public function getShortDescription() {
2317
                if (_empty($this->shortDescription)) {
2318
                        return null;
2319
                } else {
2320
                        $content = $this->shortDescription;
2321
                        if ($this->word_wrap_width > 0) $content = wordwrap($content, $this->word_wrap_width, "\n", false);
2322
                        return $content;
2323
                }
2324
        }
2325
 
2326
        protected function replaceStuff($text) {
2327
                global $argv;
2328
                $text = str_replace('$SCRIPTNAME$', $argv[0], $text);
2329
                $text = str_replace('$CURYEAR$', date('Y'), $text);
2330
                return $text;
2331
        }
2332
 
2333
        // $syntax should contain the option syntax only, no explanations.
2334
        // $SCRIPTNAME$ will be replaced by the actual script name
2335
        // $CURYEAR$ will be replaced by the current year
2336
        # FUT: Automatically generate syntax?
2337
        protected $syntax;
2338
        public function setSyntax($syntax) {
2339
                $syntax = $this->replaceStuff($syntax);
2340
                $this->syntax = $syntax;
2341
        }
2342
        public function getUsage() {
2343
                if (_empty($this->syntax)) {
2344
                        return null;
2345
                } else {
2346
                        return sprintf(VNagLang::$usage_x, $this->syntax);
2347
                }
2348
        }
2349
 
2350
        // $footNotes can be contact information or other notes which should appear in --help
2351
        protected $footNotes;
2352
        public function setFootNotes($footNotes) {
2353
                $this->footNotes = $this->replaceStuff($footNotes);
2354
        }
2355
        public function getFootNotes() {
2356
                return $this->footNotes;
2357
        }
2358
}
2359
 
2360
class VNagLang {
2361
        public static function status($code, $statusmodel) {
2362
                switch ($statusmodel) {
2363
                        case VNag::STATUSMODEL_SERVICE:
2364
                                switch ($code) {
2365
                                        case VNag::STATUS_OK:
2366
                                                return 'OK';
2367
                                                break;
2368
                                        case VNag::STATUS_WARNING:
2369
                                                return 'Warning';
2370
                                                break;
2371
                                        case VNag::STATUS_CRITICAL:
2372
                                                return 'Critical';
2373
                                                break;
2374
                                        case VNag::STATUS_UNKNOWN:
2375
                                                return 'Unknown';
2376
                                                break;
2377
                                        default:
2378
                                                return sprintf('Error (%d)', $code);
2379
                                                break;
2380
                                }
2381
                                break;
2382
                        case VNag::STATUSMODEL_HOST:
2383
                                switch ($code) {
2384
                                        case VNag::STATUS_UP:
2385
                                                return 'Up';
2386
                                                break;
2387
                                        case VNag::STATUS_DOWN:
2388
                                                return 'Down';
2389
                                                break;
2390
                                        default:
2391
                                                return sprintf('Maintain last state (%d)', $code);
2392
                                                break;
2393
                                }
2394
                                break;
2395
                        default:
2396
                                throw new VNagIllegalStatusModel(sprintf(self::$illegal_statusmodel, $statusmodel));
2397
                                break;
2398
                }
2399
        }
2400
 
2401
        static $nagios_output = 'VNag-Output';
2402
        static $verbose_info = 'Verbose information';
2403
        static $status = 'Status';
2404
        static $message = 'Message';
2405
        static $performance_data = 'Performance data';
2406
        static $status_ok = 'OK';
2407
        static $status_warn = 'Warning';
2408
        static $status_critical = 'Critical';
2409
        static $status_unknown = 'Unknown';
2410
        static $status_error = 'Error';
2411
        static $unhandled_exception_without_msg = "Unhandled exception of type %s";
2412
        static $plugin_uses = 'This plugin uses %s';
2413
        static $uses = 'uses %s';
2414
        static $x_version_x = '%s, version %s';
2415
 
2416
        // Argument names (help page)
2417
        static $argname_value = 'value';
2418
        static $argname_seconds = 'seconds';
2419
        static $certified = 'Certified by ViaThinkSoft';
2420
 
2421
        // Exceptions
2422
        static $query_without_expected_argument = "The argument '%s' is queried, but was not added to the list of expected arguments. Please contact the plugin author.";
2423
        static $required_argument_missing = "The argument '%s' is required.";
2424
        static $performance_data_invalid = 'Performance data invalid.';
2425
        static $no_standard_arguments_with_letter = "No standard argument with letter '%s' exists.";
2426
        static $invalid_start_value = 'Invalid start value.';
2427
        static $invalid_end_value = 'Invalid end value.';
2428
        static $start_is_greater_than_end = 'Start is greater than end value.';
2429
        static $value_name_forbidden = "Implementation error: You may not define a value name for the argument, because the value policy is VALUE_FORBIDDEN.";
2430
        static $value_name_required = "Implementation error: Please define a name for the argument (so it can be shown in the help page).";
2431
        static $illegal_shortopt = "Illegal shortopt '-%s'.";
2432
        static $illegal_longopt = "Illegal longopt '--%s'.";
2433
        static $illegal_valuepolicy = "valuePolicy has illegal value '%s'.";
2434
        static $range_invalid_syntax = "Syntax error in range '%s'.";
2435
        static $timeout_value_invalid = "Timeout value '%s' is invalid.";
2436
        static $range_is_invalid = 'Range is invalid.';
2437
        static $timeout_exception = 'Timeout!';
2438
        static $perfdata_label_equal_sign_forbidden = 'Label may not contain an equal sign.';
2439
        static $perfdata_value_must_be_in_class = 'Value must be in class [-0-9.] or be \'U\' if the actual value can\'t be determined.';
2440
        static $perfdata_min_must_be_in_class = 'Min must be in class [-0-9.] or empty.';
2441
        static $perfdata_max_must_be_in_class = 'Max must be in class [-0-9.] or empty.';
2442
        static $perfdata_uom_not_recognized = 'UOM (unit of measurement) "%s" is not recognized.';
2443
        static $perfdata_mixed_uom_not_implemented = 'Mixed UOMs (%s and %s) are currently not supported.';
2444
        static $no_compatible_range_uom_found = 'Measured values are not compatible with the provided warning/critical parameter. Most likely, the UOM is incompatible.';
2445
        static $exception_x = '%s (%s)';
2446
        static $no_syntax_defined = 'The author of this plugin has not defined a syntax for this plugin.';
2447
        static $usage_x = "Usage:\n%s";
2448
        static $options = "Options:";
2449
        static $illegal_statusmodel = "Invalid statusmodel %d.";
2450
        static $none = '[none]';
2451
        static $valueUomPairSyntaxError = 'Syntax error at "%s". Syntax must be Value[UOM].';
2452
        static $too_few_warning_ranges = "You have too few warning ranges (currently trying to get element %d).";
2453
        static $too_few_critical_ranges = "You have too few critical ranges (currently trying to get element %d).";
2454
        static $dataset_missing = 'Dataset missing.';
2455
        static $payload_not_base64 = 'The payload is not valid Base64.';
2456
        static $payload_not_json = 'The payload is not valid JSON.';
2457
        static $signature_missing = 'The signature is missing.';
2458
        static $signature_not_bas64 = 'The signature is not valid Base64.';
2459
        static $signature_invalid = 'The signature is invalid. The connection might have been tampered, or a different key is used.';
2460
        static $pubkey_file_not_found = "Public key file %s was not found.";
2461
        static $pubkey_file_not_readable = "Public key file %s is not readable.";
2462
        static $privkey_file_not_found = "Private key file %s was not found.";
2463
        static $privkey_file_not_readable = "Private key file %s is not readable.";
2464
        static $perfdata_line_invalid = "Performance data line %s is invalid.";
2465
        static $singlevalue_unexpected_at_symbol = 'This plugin does not allow the @-symbol at ranges for single values.';
2466
        static $illegalSingleValueBehavior = "Illegal value for 'singleValueBehavior'. Please contact the creator of the plugin.";
2467
        static $dataset_encryption_no_array = 'Dataset encryption information invalid.';
2468
        static $require_password = 'This resource is protected with a password. Please provide a password.';
2469
        static $wrong_password = 'This resource is protected with a password. You have provided the wrong password, or it was changed.';
2470
        static $convert_x_y_error = 'Cannot convert from UOM %s to UOM %s.';
2471
        static $php_error = 'PHP has detected an error in the plugin. Please contact the plugin author.';
2472
        static $output_level_lowered = "Output Buffer level lowered during cbRun(). Please contact the plugin author.";
21 daniel-mar 2473
        static $openssl_missing = "OpenSSL is missing. Therefore, encryption and signatures are not available.";
2 daniel-mar 2474
 
2475
        // Help texts
2476
        static $warning_range = 'Warning range';
2477
        static $critical_range = 'Critical range';
2478
        static $prints_version = 'Prints version';
2479
        static $verbosity_helptext = 'Verbosity -v, -vv or -vvv';
2480
        static $timeout_helptext = 'Sets timeout in seconds';
2481
        static $help_helptext = 'Prints help page';
2482
        static $prints_usage = 'Prints usage';
2483
 
2484
        static $notConstructed = 'Parent constructor not called with parent::__construct().';
2485
}
2486
 
2487
function vnagErrorHandler($errorkind, $errortext, $file, $line) {
20 daniel-mar 2488
        // This function "converts" PHP runtime errors into Exceptions, which can then be handled by VNag::handleException()
19 daniel-mar 2489
        global $inside_vnag_run;
20 daniel-mar 2490
 
19 daniel-mar 2491
        if (!$inside_vnag_run && VNag::is_http_mode()) {
2492
                // We want to avoid that the VNag-Exception will show up in a website that contains
2493
                // an embedded VNag monitor, so if we are not inside a running VNag code,
2494
                // we will call the normal PHP error handler.
2495
                return false;
2496
        }
20 daniel-mar 2497
 
2 daniel-mar 2498
        if (!(error_reporting() & $errorkind)) {
2499
                // Code is not included in error_reporting. Don't do anything.
19 daniel-mar 2500
                return true;
2 daniel-mar 2501
        }
2502
 
2503
        // We want 100% clean scripts, so any error, warning or notice will shutdown the script
2504
        // This also fixes the issue that PHP will end with result code 0, showing an error.
2505
 
2506
        // Error kinds see http://php.net/manual/en/errorfunc.constants.php
2507
        if (defined('E_ERROR') && ($errorkind == E_ERROR)) $errorkind = 'Error';
2508
        if (defined('E_WARNING') && ($errorkind == E_WARNING)) $errorkind = 'Warning';
2509
        if (defined('E_PARSE') && ($errorkind == E_PARSE)) $errorkind = 'Parse';
2510
        if (defined('E_NOTICE') && ($errorkind == E_NOTICE)) $errorkind = 'Notice';
2511
        if (defined('E_CORE_ERROR') && ($errorkind == E_CORE_ERROR)) $errorkind = 'Core Error';
2512
        if (defined('E_CORE_WARNING') && ($errorkind == E_CORE_WARNING)) $errorkind = 'Core Warning';
2513
        if (defined('E_COMPILE_ERROR') && ($errorkind == E_COMPILE_ERROR)) $errorkind = 'Compile Error';
2514
        if (defined('E_COMPILE_WARNING') && ($errorkind == E_COMPILE_WARNING)) $errorkind = 'Compile Warning';
2515
        if (defined('E_USER_ERROR') && ($errorkind == E_USER_ERROR)) $errorkind = 'User Error';
2516
        if (defined('E_USER_WARNING') && ($errorkind == E_USER_WARNING)) $errorkind = 'User Warning';
2517
        if (defined('E_USER_NOTICE') && ($errorkind == E_USER_NOTICE)) $errorkind = 'User Notice';
2518
        if (defined('E_STRICT') && ($errorkind == E_STRICT)) $errorkind = 'Strict';
2519
        if (defined('E_RECOVERABLE_ERROR') && ($errorkind == E_RECOVERABLE_ERROR)) $errorkind = 'Recoverable Error';
2520
        if (defined('E_DEPRECATED') && ($errorkind == E_DEPRECATED)) $errorkind = 'Deprecated';
2521
        if (defined('E_USER_DEPRECATED') && ($errorkind == E_USER_DEPRECATED)) $errorkind = 'User Deprecated';
2522
        throw new VNagException(VNagLang::$php_error . " $errortext at $file:$line (kind $errorkind)");
2523
 
19 daniel-mar 2524
        // true = the PHP internal error handling will NOT be called.
2 daniel-mar 2525
        return true;
2526
}
2527
 
19 daniel-mar 2528
$inside_vnag_run = false;
2 daniel-mar 2529
$old_error_handler = set_error_handler("vnagErrorHandler");
2530
 
2531
// === End of document ===