Subversion Repositories vnag

Rev

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

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