Rev 59 | 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> |
70 | daniel-mar | 2 | Dn0TAtS8an72QeGrW4rRe6KqVYdOn1iBSJMs07mG3B21qZSA77cSJqdqIvni4+mQ+ |
3 | k3ee/H/eGuu/j6XsdzIRNXk+p+CjfSaXf9jn0VwWXhurSgRzF7uOIbhv3jpKI9mss |
||
4 | dY4aQVblJiTTKuN27AB7LjqSSmJyEidWDYVe35g79sj76MJMP5Dz3+dcZSlcqhDrH |
||
5 | 6T+uR5aoz09Kzro50iZ3M6n1e4EY9hDJH+Bqdj8kJRk0qGGt+XFHwqEAahJMiE1Sq |
||
6 | eFZmiwhEBmck70HpC2gcmrm5L3cp5pv7z3UFGXmi2QoKyzZMQKfyw2BD+uGv9uKR4 |
||
7 | LE5sdpJy4ZsHjRzKuKXmu+eLNEh8loDzYzIRBnikk9NV9FrO9Rraw14aVhcRvxY5K |
||
8 | j3CmReIweYJRqJ9kfEjTh5RMMut33ZazaIw1rgGvKSNq5HVgP/tPc3Nx1RIEmgBu5 |
||
9 | e5gB+ou9i4w01gi+b5Akw/yWsTrr+JE83j1BXWaDXLtenaCRAMGB7/q/kEcFgoJlt |
||
10 | qSxcMFmvTExlFldfF9ohXlE52F9AVQJv76AYu86FTZH8qQFMn6Z2yL/HHrBJvpEsW |
||
11 | AsKOWsvKH9x8EZLQKzwuaQRGX01Z936d2V/e7Rb+EJjmUbQ/9TJrpYSzAZB4PCPnU |
||
12 | YHeWNMwIHi82MDwG+7ey+bF9A0DJyosa0f+tgQ0szpoOJgCXZNm+guCYFkp5rnbWs |
||
13 | 16VlVsqYxn20yFUX3uci7s31ToIqJfLGpZKi1yaWtavhW2Jb6SxuMU7nsaQgsyv6J |
||
14 | VR2eJ49itgeTpArGWLscTJ9BHDeOjhkLqoVPVWut2XJF8mJTn26pnvIn04997JL+I |
||
15 | e7FSV+6mtWLae+fh7A/aASM/4PJQhKqBARZtGAsgH111z4iqE4psACt6Z62ekgO7O |
||
16 | /slAwR+9Je7VT/CZdOoNnShNIYGlVgm1cKnHxAP7H7vBDhMmTbTeZUtdZU2hOAJME |
||
17 | 1C99Q4btFMnE1nW5oB9jQpeKXZ6DEivyNsy7gG+YPqauMlOu30gXYx7V4juAaBvlO |
||
18 | kGwJ1jpqsaPI4C0o7smkksy6WHWGsRioTgxc+vOBnqOczYWaste5TlFIEaW4KAHcg |
||
19 | 3hEeebMPeNWhgUp/uJnfpEuSt3M89tEhD6Hr1LxNY5dflperss3LeXAtCuuqdT6qu |
||
20 | STrB6iTCcAvngMfqyZKrnrq1xhraH+b7iC8qpmDZIPM1pjoCTJjVa6DrEmmh/3+mQ |
||
21 | ZuOwqRNWSmhvMf4V1gzx4py7KCPP+bwMe8fphXG9Qu3Drl0vV13PB1QXzDkMAQ5zn |
||
22 | +LZva+1J5Q33Xu1DH8ih2rqFYdhKYS+Er9KP1iJsTmpFoJHe+b8IXzI++MeL0WGEV |
||
23 | w== |
||
4 | daniel-mar | 24 | </ViaThinkSoftSignature> */ ?> |
2 | daniel-mar | 25 | <?php |
26 | |||
27 | /* |
||
28 | * VNag - Nagios Framework for PHP |
||
29 | * Developed by Daniel Marschall, ViaThinkSoft <www.viathinksoft.com> |
||
30 | * Licensed under the terms of the Apache 2.0 license |
||
31 | * |
||
59 | daniel-mar | 32 | * Revision 2022-12-18 |
2 | daniel-mar | 33 | * |
34 | * Changelog: |
||
35 | * 2018-08-01 1.0 Initial release |
||
36 | * 2018-09-02 1.1 Added argument -e|--emptyok |
||
37 | * Output a warning if the Linux user does not exist. |
||
38 | * 2018-10-01 1.2 Fixed a bug where too many unnecessary requests were sent to ipinfo.io |
||
39 | * Cache file location ~/.last_ipcache is now preferred |
||
40 | * A token for ipinfo.io can now be provided |
||
41 | * 2018-11-03 1.2.1 "system boot" lines are now excluded |
||
59 | daniel-mar | 42 | * 2022-12-18 1.2.2 Use the new cache directory now |
2 | daniel-mar | 43 | */ |
44 | |||
45 | // QUE: should we allow usernames to have wildcards, regexes or comma separated? |
||
46 | |||
47 | declare(ticks=1); |
||
48 | |||
49 | class LastCheck extends VNag { |
||
50 | private $argUser = null; |
||
51 | private $argRegex = null; |
||
52 | private $argWtmpFiles = null; |
||
53 | private $argEmptyOk = null; |
||
54 | private $argIpInfoToken = null; |
||
55 | |||
56 | private $cache = null; |
||
57 | private $cacheFile = null; |
||
58 | private $cacheDirty = false; |
||
59 | |||
60 | public function __construct() { |
||
61 | parent::__construct(); |
||
62 | |||
63 | if ($this->is_http_mode()) { |
||
64 | // Don't allow the standard arguments via $_REQUEST |
||
65 | $this->registerExpectedStandardArguments(''); |
||
66 | } else { |
||
67 | $this->registerExpectedStandardArguments('Vhtv'); |
||
68 | } |
||
69 | |||
70 | $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)); |
||
71 | $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)); |
||
72 | $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*')); |
||
73 | $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)); |
||
74 | $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)); |
||
75 | |||
76 | $this->getHelpManager()->setPluginName('vnag_last'); |
||
77 | $this->getHelpManager()->setVersion('1.2'); |
||
78 | $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.'); |
||
79 | $this->getHelpManager()->setCopyright('Copyright (C) 2011-$CURYEAR$ Daniel Marschall, ViaThinkSoft.'); |
||
80 | $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-v] [-e] [-u username] [-R regex] [--ipInfoToken token]'); |
||
81 | $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com'); |
||
82 | |||
59 | daniel-mar | 83 | $this->cacheFile = $this->get_cache_dir() . '/.last_ip_cache'; |
84 | if (!file_exists($this->cacheFile)) @touch($this->cacheFile); |
||
2 | daniel-mar | 85 | $this->cache = $this->cacheFile ? json_decode(file_get_contents($this->cacheFile),true) : array(); |
86 | } |
||
87 | |||
88 | public function __destruct() { |
||
89 | if ($this->cacheFile && $this->cacheDirty) { |
||
90 | @file_put_contents($this->cacheFile, json_encode($this->cache)); |
||
91 | } |
||
92 | } |
||
93 | |||
94 | private function getCountryAndOrg($ip) { |
||
95 | if (isset($this->cache[$ip])) return $this->cache[$ip]; |
||
96 | |||
97 | $url = 'https://ipinfo.io/'.urlencode($ip).'/json'; |
||
98 | $token = $this->argIpInfoToken->getValue(); |
||
99 | if ($token) $url .= '?token='.urlencode($token); |
||
100 | |||
101 | // fwrite(STDERR, "Note: Will query $url\n"); |
||
102 | $cont = file_get_contents($url); |
||
103 | if (!$cont) return array(); |
||
104 | if (!($data = @json_decode($cont, true))) return array(); |
||
105 | if (isset($data['error'])) return array(); |
||
106 | |||
107 | if (isset($data['bogon']) && ($data['bogon'])) { |
||
108 | // Things like 127.0.0.1 do not belong to anyone |
||
109 | $res = array(); |
||
110 | } else { |
||
111 | $res = array(); |
||
112 | if (isset($data['hostname'])) $res[] = $data['hostname']; |
||
113 | if (isset($data['country'])) $res[] = $data['country']; |
||
114 | list($as, $orgName) = explode(' ', $data['org'], 2); |
||
115 | $res[] = $as; |
||
116 | $res[] = $orgName; |
||
117 | } |
||
118 | |||
119 | $this->cache[$ip] = $res; |
||
120 | $this->cacheDirty = true; |
||
121 | return $res; |
||
122 | } |
||
123 | |||
124 | private function getLastLoginIPs($username) { |
||
125 | $cont = ''; |
||
126 | $files = glob($this->argWtmpFiles->getValue()); |
||
127 | foreach ($files as $file) { |
||
128 | if (trim($username) == '') { |
||
129 | $cont .= shell_exec('last -f '.escapeshellarg($file).' -F -w '); // all users |
||
130 | } else { |
||
131 | $cont .= shell_exec('last -f '.escapeshellarg($file).' -F -w '.escapeshellarg($username)); |
||
132 | } |
||
133 | } |
||
70 | daniel-mar | 134 | |
2 | daniel-mar | 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 |
||
70 | daniel-mar | 139 | // reboot system boot 6.1.0-11-amd64 Fri Sep 8 13:10:27 2023 - Sat Sep 9 17:40:50 2023 (1+04:30) |
2 | daniel-mar | 140 | unset($m[$key]); |
70 | daniel-mar | 141 | //} else if ($a[2] === 'begins') { |
142 | } else if (substr($a[1],0,4) === 'wtmp') { |
||
143 | // wtmp.1 begins Fri Oct 12 02:10:43 2018 (English) |
||
144 | // wtmp beginnt Wed Aug 16 11:43:03 2023 (German) |
||
2 | daniel-mar | 145 | unset($m[$key]); |
146 | } else { |
||
147 | array_shift($a); |
||
148 | } |
||
149 | } |
||
150 | return $m; |
||
151 | } |
||
152 | |||
153 | protected function cbRun() { |
||
154 | if (!`which which`) { |
||
155 | throw new VNagException("Program 'which' is not installed on your system"); |
||
156 | } |
||
157 | |||
158 | if (!`which last`) { |
||
159 | throw new VNagException("Program 'last' (usually included in package smartmontools) is not installed on your system"); |
||
160 | } |
||
161 | |||
162 | $username = $this->argUser->available() ? $this->argUser->getValue() : ''; |
||
163 | $regex = $this->argRegex->available() ? $this->argRegex->getValue() : null; |
||
164 | |||
165 | if (($username != '') && function_exists('posix_getpwnam') && !posix_getpwnam($username)) { |
||
166 | $this->setStatus(VNag::STATUS_WARNING); |
||
167 | $this->addVerboseMessage("WARNING: Currently, there is no Linux user with name '$username'.", VNag::VERBOSITY_SUMMARY); |
||
168 | } |
||
169 | |||
170 | $count_total = 0; |
||
171 | $count_ok = 0; |
||
172 | $count_warning = 0; |
||
173 | |||
174 | foreach ($this->getLastLoginIPs($username) as list($username, $pts, $ip, $times)) { |
||
36 | daniel-mar | 175 | // IP ":pts/0:S.0" means that there is a screen session |
176 | if (strpos($ip,':pts/') === 0) continue; |
||
177 | |||
2 | daniel-mar | 178 | $count_total++; |
179 | |||
180 | $fields = $this->getCountryAndOrg($ip); |
||
181 | $fields[] = $ip; |
||
182 | |||
183 | if (is_null($regex)) { |
||
184 | // No regex. Just show the logins for information (status stays VNag::STATUS_UNKNOWN) |
||
185 | $this->addVerboseMessage("INFO: ".implode(' ',$fields)." @ $username, $pts $times", VNag::VERBOSITY_ADDITIONAL_INFORMATION); |
||
186 | } else { |
||
187 | $match = false; |
||
188 | foreach ($fields as $f) { |
||
189 | if (preg_match($regex, $f, $dummy)) { |
||
190 | $match = true; |
||
191 | break; |
||
192 | } |
||
193 | } |
||
194 | |||
195 | if ($match) { |
||
196 | $count_ok++; |
||
197 | $this->addVerboseMessage("OK: ".implode(' ',$fields)." @ $username $pts $times", VNag::VERBOSITY_ADDITIONAL_INFORMATION); |
||
198 | $this->setStatus(VNag::STATUS_OK); |
||
199 | } else { |
||
200 | $count_warning++; |
||
201 | $this->addVerboseMessage("WARNING: ".implode(' ',$fields)." @ $username $pts $times", VNag::VERBOSITY_SUMMARY); |
||
202 | $this->setStatus(VNag::STATUS_WARNING); |
||
203 | } |
||
204 | } |
||
205 | } |
||
206 | |||
207 | if (is_null($regex)) { |
||
208 | $this->setHeadline("Checked $count_total logins (No checks done because argument 'Regex' is missing)"); |
||
209 | } else { |
||
210 | if (($count_total == 0) && ($this->argEmptyOk->count() > 0)) { |
||
211 | $this->setStatus(VNag::STATUS_OK); |
||
212 | } |
||
213 | $this->setHeadline("Checked $count_total logins ($count_ok OK, $count_warning Warning)"); |
||
214 | } |
||
215 | } |
||
216 | } |