Subversion Repositories vnag

Rev

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

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