Subversion Repositories vnag

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php /* <ViaThinkSoftSignature>akRkqMquPkfxpwy3lnAlcQ1qVAUdTJBHZNOG+o13yNrgyHRqadEskkSxmItifQzCENCOBCXf4QUqo8xtFT0uxyzv/R0ZrB8H0dF+2LslQ6eYj+IrPgbiQoFY4FoxAS24ZCpH8v+GYD4lq/UmU+c7eggeNGtv9ZYkQaz2ZUS4hT0nrP7qO2quF1dpm+8dsB5pKj9iY/ujHP9iEWzn3d8BhgkaSed+GBkqurqY1lc+2l2tCsT1ZPEOR41uWLeSya1WGwEAWy6/2ih4x5dG3lsNgKRcJyDdcDYw+mhXq3vxPeqcMWFiplq6cF9XxxY3NHOk6HcIO7qR52jOkU7Kvy31CPso0C12h0xFgo9fvj3BJUJWZvSw00I1SqRMJn2o2+KtVCWSfMGJ5brgRCMNdHDiCCbkFaRtElsv1q0ewTLotrRohZxJpRwoOK7dEDsR+V0hhOfYzZ/zRaNqG/LPIqSeBsZhA7km8IpZlwSbUom2PKKWdL8YBwSdqjtgM9Z3B0TQDkhSvU17mICuIgjQfeMwxyaUHFYvA+yqofcy9g2GQHTj15B2s39gnW9pKgVK35lRKOCoQ6rYptBfHUXH02VjSLqcFgoV7cOamBftfC55+RRpvfZURQUvgWbBIYtQ3HDeze775vdbr85HL6ADKA2Rdd/HSG1E+GMuXUokcWOxg80ih+HnnrGqfFcohmfs1qy6ZZtYQBnJszFZATsMF+AdztxJahbVf7lrM36Zl22kXpkz8lX2lp382bBvBBYwWUaf3j+UBLWHoFfnOjc1BnxECvuAIlgiD7mRIYIx/WQxlF1MU3ByrVuIzfOR2j3Xy+Iv0ettCVNSQdCnN+B2WV/Hqtj1qLsXgZcxdYatlqDKLytyk2bSyX/WLnRj5xnjWEGDPAoblIjknHz9BiDWRYNdSqZ1skixKIZTbynhqWrG9dKSPvDfdXpvguRB6tSWK3/70aOfWf3TnmTBHXS3/NvGzV+3WtZfujCXTxBSCuV6ReTIuqUbKoVT/rCX5sMNMDuElsbHjg031VKNyqjx3L9F/fMopue8G1vu2QCsjiGU59EwtakcrobMyopZwV8eqe3/By7SjVVL11MquTEV+qIWV3gNtlFFroCO+ztVdKj1+FMsZtZfDhHkxeHG/sx64CW7B1mw5pPCr3FriDScWy3XfD29ah1Tx0/6AsHMViLNCMK65bP5ttp7BBb2vnGX68vc9qMNCBCIhR9lOwPobtzB4MpVAHX6dEn/3XVhkoW7O7jcz3CkgZOZJv8ymKE4SUw7s/ns6w/m4wyujDdp9343WuALsxrxXMswqiTx7v1hmeJccT9dRMJYGQ7DRnB8Z1K+px6FALfwMg7OMcbQh+wiwg==</ViaThinkSoftSignature> */ ?>
2
<?php
3
 
4
/*
5
 * VNag - Nagios Framework for PHP
6
 * Developed by Daniel Marschall, ViaThinkSoft <www.viathinksoft.com>
7
 * Licensed under the terms of the Apache 2.0 license
8
 *
9
 * Revision 2018-11-03
10
 *
11
 * Changelog:
12
 * 2018-08-01   1.0   Initial release
13
 * 2018-09-02   1.1   Added argument -e|--emptyok
14
 *                    Output a warning if the Linux user does not exist.
15
 * 2018-10-01   1.2   Fixed a bug where too many unnecessary requests were sent to ipinfo.io
16
 *                    Cache file location ~/.last_ipcache is now preferred
17
 *                    A token for ipinfo.io can now be provided
18
 * 2018-11-03   1.2.1 "system boot" lines are now excluded
19
 */
20
 
21
// QUE: should we allow usernames to have wildcards, regexes or comma separated?
22
 
23
declare(ticks=1);
24
 
25
class LastCheck extends VNag {
26
        private $argUser = null;
27
        private $argRegex = null;
28
        private $argWtmpFiles = null;
29
        private $argEmptyOk = null;
30
        private $argIpInfoToken = null;
31
 
32
        private $cache = null;
33
        private $cacheFile = null;
34
        private $cacheDirty = false;
35
 
36
        protected function getIpCacheFile() {
37
                $homedir = @getenv('HOME');
38
                if ($homedir) {
39
                        $try = "${homedir}/.last_ipcache";
40
                        if (file_exists($try)) return $try;
41
                        if (@touch($try)) return $try;
42
                }
43
 
44
                $user = posix_getpwuid(posix_geteuid());
45
                if (isset($user['dir'])) {
46
                        $homedir = $user['dir'];
47
                        $try = "${homedir}/.last_ipcache";
48
                        if (file_exists($try)) return $try;
49
                        if (@touch($try)) return $try;
50
                }
51
 
52
                if (isset($user['name'])) {
53
                        $username = $user['name'];
54
                        $try = "/tmp/ipcache_${username}.tmp";
55
                        if (file_exists($try)) return $try;
56
                        if (@touch($try)) return $try;
57
                }
58
 
59
                return false; // should usually never happen
60
        }
61
 
62
        public function __construct() {
63
                parent::__construct();
64
 
65
                if ($this->is_http_mode()) {
66
                        // Don't allow the standard arguments via $_REQUEST
67
                        $this->registerExpectedStandardArguments('');
68
                } else {
69
                        $this->registerExpectedStandardArguments('Vhtv');
70
                }
71
 
72
                $this->addExpectedArgument($this->argUser = new VNagArgument('u', 'user', VNagArgument::VALUE_REQUIRED, 'user', 'The Linux username. If the argument is missing, all users will be checked.', null));
73
                $this->addExpectedArgument($this->argRegex = new VNagArgument('R', 'regex', VNagArgument::VALUE_REQUIRED, 'regex', 'The regular expression (in PHP: preg_match) which is applied on IP, Hostname, Country, AS number or ISP name. If the regular expression matches, the login will be accepted, otherweise an alert will be triggered. Example: /DE/ismU or /Telekom/ismU', null));
74
                $this->addExpectedArgument($this->argWtmpFiles = new VNagArgument('f', 'wtmpfile', VNagArgument::VALUE_REQUIRED, 'wtmpfile', 'Filemask of the wtmp file (important if you use logrotate), e.g. \'/var/log/wtmp*\'', '/var/log/wtmp*'));
75
                $this->addExpectedArgument($this->argEmptyOk = new VNagArgument('e', 'emptyok', VNagArgument::VALUE_FORBIDDEN, null, 'Treat an empty result (e.g. empty wtmp file after rotation) as success; otherwise treat it as status "Unknown"', null));
76
                $this->addExpectedArgument($this->argIpInfoToken = new VNagArgument(null, 'ipInfoToken', VNagArgument::VALUE_REQUIRED, 'token', 'If you have a token for ipinfo.io, please enter it here. Without token, you can query the service approx 1,000 times per day (which should be enough)', null));
77
 
78
                $this->getHelpManager()->setPluginName('vnag_last');
79
                $this->getHelpManager()->setVersion('1.2');
80
                $this->getHelpManager()->setShortDescription('This plugin checks the logs of the tool "LAST" an warns when users have logged in with an unexpected IP/Country/ISP.');
81
                $this->getHelpManager()->setCopyright('Copyright (C) 2011-$CURYEAR$ Daniel Marschall, ViaThinkSoft.');
82
                $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-v] [-e] [-u username] [-R regex] [--ipInfoToken token]');
83
                $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com');
84
 
85
                $this->cacheFile = $this->getIpCacheFile();
86
                $this->cache = $this->cacheFile ? json_decode(file_get_contents($this->cacheFile),true) : array();
87
        }
88
 
89
        public function __destruct() {
90
                if ($this->cacheFile && $this->cacheDirty) {
91
                        @file_put_contents($this->cacheFile, json_encode($this->cache));
92
                }
93
        }
94
 
95
        private function getCountryAndOrg($ip) {
96
                if (isset($this->cache[$ip])) return $this->cache[$ip];
97
 
98
                $url = 'https://ipinfo.io/'.urlencode($ip).'/json';
99
                $token = $this->argIpInfoToken->getValue();
100
                if ($token) $url .= '?token='.urlencode($token);
101
 
102
                // fwrite(STDERR, "Note: Will query $url\n");
103
                $cont = file_get_contents($url);
104
                if (!$cont) return array();
105
                if (!($data = @json_decode($cont, true))) return array();
106
                if (isset($data['error'])) return array();
107
 
108
                if (isset($data['bogon']) && ($data['bogon'])) {
109
                        // Things like 127.0.0.1 do not belong to anyone
110
                        $res = array();
111
                } else {
112
                        $res = array();
113
                        if (isset($data['hostname'])) $res[] = $data['hostname'];
114
                        if (isset($data['country'])) $res[] = $data['country'];
115
                        list($as, $orgName) = explode(' ', $data['org'], 2);
116
                        $res[] = $as;
117
                        $res[] = $orgName;
118
                }
119
 
120
                $this->cache[$ip] = $res;
121
                $this->cacheDirty = true;
122
                return $res;
123
        }
124
 
125
        private function getLastLoginIPs($username) {
126
                $cont = '';
127
                $files = glob($this->argWtmpFiles->getValue());
128
                foreach ($files as $file) {
129
                        if (trim($username) == '') {
130
                                $cont .= shell_exec('last -f '.escapeshellarg($file).' -F -w '); // all users
131
                        } else {
132
                                $cont .= shell_exec('last -f '.escapeshellarg($file).' -F -w '.escapeshellarg($username));
133
                        }
134
                }
135
                preg_match_all('@^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$@ismU', $cont, $m, PREG_SET_ORDER);
136
                foreach ($m as $key => &$a) {
137
                        if (($a[2] === 'system') && ($a[3] === 'boot')) {
138
                                // reboot   system boot  4.9.0-8-amd64    Fri Oct 12 02:10   still running
139
                                unset($m[$key]);
140
                        } else if ($a[2] === 'begins') {
141
                                // wtmp.1 begins Fri Oct 12 02:10:43 2018
142
                                unset($m[$key]);
143
                        } else {
144
                                array_shift($a);
145
                        }
146
                }
147
                return $m;
148
        }
149
 
150
        protected function cbRun() {
151
                if (!`which which`) {
152
                        throw new VNagException("Program 'which' is not installed on your system");
153
                }
154
 
155
                if (!`which last`) {
156
                        throw new VNagException("Program 'last' (usually included in package smartmontools) is not installed on your system");
157
                }
158
 
159
                $username = $this->argUser->available() ? $this->argUser->getValue() : '';
160
                $regex = $this->argRegex->available() ? $this->argRegex->getValue() : null;
161
 
162
                if (($username != '') && function_exists('posix_getpwnam') && !posix_getpwnam($username)) {
163
                        $this->setStatus(VNag::STATUS_WARNING);
164
                        $this->addVerboseMessage("WARNING: Currently, there is no Linux user with name '$username'.", VNag::VERBOSITY_SUMMARY);
165
                }
166
 
167
                $count_total = 0;
168
                $count_ok = 0;
169
                $count_warning = 0;
170
 
171
                foreach ($this->getLastLoginIPs($username) as list($username, $pts, $ip, $times)) {
172
                        $count_total++;
173
 
174
                        $fields = $this->getCountryAndOrg($ip);
175
                        $fields[] = $ip;
176
 
177
                        if (is_null($regex)) {
178
                                // No regex. Just show the logins for information (status stays VNag::STATUS_UNKNOWN)
179
                                $this->addVerboseMessage("INFO: ".implode(' ',$fields)." @ $username, $pts $times", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
180
                        } else {
181
                                $match = false;
182
                                foreach ($fields as $f) {
183
                                        if (preg_match($regex, $f, $dummy)) {
184
                                                $match = true;
185
                                                break;
186
                                        }
187
                                }
188
 
189
                                if ($match) {
190
                                        $count_ok++;
191
                                        $this->addVerboseMessage("OK: ".implode(' ',$fields)." @ $username $pts $times", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
192
                                        $this->setStatus(VNag::STATUS_OK);
193
                                } else {
194
                                        $count_warning++;
195
                                        $this->addVerboseMessage("WARNING: ".implode(' ',$fields)." @ $username $pts $times", VNag::VERBOSITY_SUMMARY);
196
                                        $this->setStatus(VNag::STATUS_WARNING);
197
                                }
198
                        }
199
                }
200
 
201
                if (is_null($regex)) {
202
                        $this->setHeadline("Checked $count_total logins (No checks done because argument 'Regex' is missing)");
203
                } else {
204
                        if (($count_total == 0) && ($this->argEmptyOk->count() > 0)) {
205
                                $this->setStatus(VNag::STATUS_OK);
206
                        }
207
                        $this->setHeadline("Checked $count_total logins ($count_ok OK, $count_warning Warning)");
208
                }
209
        }
210
}