1<?php
2
3/**
4 * Observium
5 *
6 *   This file is part of Observium.
7 *
8 * @package    observium
9 * @subpackage syslog
10 * @author     Adam Armstrong <adama@observium.org>
11 * @copyright  (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited
12 *
13 */
14
15// FIXME use db functions properly
16
17// DOCME needs phpdoc block
18// TESTME needs unit testing
19function get_cache($host, $value)
20{
21  global $dev_cache;
22
23  if (empty($host)) { return; }
24
25  // Check cache expiration
26  $now = time();
27  $expired = TRUE;
28  if (isset($dev_cache[$host]['lastchecked']))
29  {
30    if (($now - $dev_cache[$host]['lastchecked']) < 600) { $expired = FALSE; } // will expire after 10 min
31  }
32  if ($expired) { $dev_cache[$host]['lastchecked'] = $now; }
33
34  if (!isset($dev_cache[$host][$value]) || $expired)
35  {
36    switch($value)
37    {
38      case 'device_id':
39        // Try by map in config
40        if (isset($GLOBALS['config']['syslog']['host_map'][$host]))
41        {
42          $new_host = $GLOBALS['config']['syslog']['host_map'][$host];
43          if (is_numeric($new_host))
44          {
45            // Check if device id exist
46            $dev_cache[$host]['device_id'] = dbFetchCell('SELECT `device_id` FROM `devices` WHERE `device_id` = ?', array($new_host));
47          } else {
48            $dev_cache[$host]['device_id'] = dbFetchCell('SELECT `device_id` FROM `devices` WHERE `hostname` = ? OR `sysName` = ?', array($new_host, $new_host));
49          }
50          // If syslog host map correct, return device id or try onward
51          if ($dev_cache[$host]['device_id'])
52          {
53            return $dev_cache[$host]['device_id'];
54          }
55        }
56
57        // Localhost IPs, try detect as local system
58        if (in_array($host, array('127.0.0.1', '::1')))
59        {
60          if ($localhost_id = dbFetchCell('SELECT `device_id` FROM `devices` WHERE `hostname` = ?', array(get_localhost())))
61          {
62            $dev_cache[$host]['device_id'] = $localhost_id;
63          }
64          else if ($localhost_id = dbFetchCell('SELECT `device_id` FROM `devices` WHERE `sysName` = ?', array(get_localhost())))
65          {
66            $dev_cache[$host]['device_id'] = $localhost_id;
67          }
68          // NOTE in other cases localhost IPs associated with random device
69        } else {
70          // Try by hostname
71          $dev_cache[$host]['device_id'] = dbFetchCell('SELECT `device_id` FROM `devices` WHERE `hostname` = ? OR `sysName` = ?', array($host, $host));
72        }
73
74        // If failed, try by IP
75        if (!is_numeric($dev_cache[$host]['device_id']))
76        {
77          $ip = $host;
78
79          $ip_version = get_ip_version($ip);
80          if ($ip_version !== FALSE)
81          {
82            if ($ip_version == 6 && preg_match('/::ffff:(\d+\.\d+\.\d+\.\d+)/', $ip, $matches))
83            {
84              // IPv4 mapped to IPv6, like ::ffff:192.0.2.128
85              // See: http://jira.observium.org/browse/OBSERVIUM-1274
86              $ip = $matches[1];
87              $ip_version = 4;
88            }
89            else if ($ip_version == 6)
90            {
91              $ip = Net_IPv6::uncompress($ip, TRUE);
92            }
93
94            // Detect associated device by IP address, exclude deleted ports
95            // IS NULL allow to search addresses without associated port
96            $query = 'SELECT * FROM `ipv'.$ip_version.'_addresses` LEFT JOIN `ports` USING (`port_id`) WHERE `ipv'.$ip_version.'_address` = ? AND (`deleted` = ? OR `deleted` IS NULL);';
97            $addresses = dbFetchRows($query, array($ip, 0));
98            $address_count = count($addresses);
99
100            if ($address_count)
101            {
102              $dev_cache[$host]['device_id'] = $addresses[0]['device_id'];
103
104              // Additional checks if multiple addresses found
105              if ($address_count > 1)
106              {
107                foreach ($addresses as $entry)
108                {
109                  $device_tmp = device_by_id_cache($entry['device_id']);
110                  if ($device_tmp['disabled'] || !$device_tmp['status'])                      { continue; } // Skip disabled and down devices
111                  else if ($entry['ifAdminStatus'] == 'down' ||                                             // Skip disabled ports
112                           in_array($entry['ifOperStatus'], array('down', 'lowerLayerDown'))) { continue; } // Skip down ports
113
114                  // Override cached host device_id
115                  $dev_cache[$host]['device_id'] = $entry['device_id'];
116                  break; // End loop on first founded entry
117                }
118                unset($device_tmp);
119              }
120
121            }
122          }
123        }
124        break;
125      case 'os':
126      case 'version':
127        if ($device_id = get_cache($host, 'device_id'))
128        {
129          $dev_cache[$host][$value] = dbFetchCell('SELECT `'.$value.'` FROM `devices` WHERE `device_id` = ?', array($device_id));
130        } else {
131          return NULL;
132        }
133        break;
134      case 'os_group':
135        $os = get_cache($host, 'os');
136        $dev_cache[$host]['os_group'] = (isset($GLOBALS['config']['os'][$os]['group']) ? $GLOBALS['config']['os'][$os]['group'] : '');
137        break;
138      default:
139        return NULL;
140    }
141  }
142
143  return $dev_cache[$host][$value];
144}
145
146function cache_syslog_rules()
147{
148
149  $rules = array();
150  foreach(dbFetchRows("SELECT * FROM `syslog_rules` WHERE `la_disable` = ?", array('0')) as $lat)
151  {
152    $rules[$lat['la_id']] = $lat;
153  }
154
155  return $rules;
156
157}
158
159function cache_syslog_rules_assoc()
160{
161  $device_rules = array();
162  foreach (dbFetchRows("SELECT * FROM `syslog_rules_assoc`") as $laa)
163  {
164
165    //print_r($laa);
166
167    if ($laa['entity_type'] == 'group')
168    {
169      $devices = get_group_entities($laa['entity_id']);
170      foreach($devices as $dev_id)
171      {
172        $device_rules[$dev_id][$laa['la_id']] = TRUE;
173      }
174    }
175    else if ($laa['entity_type'] == 'device')
176    {
177      $device_rules[$laa['entity_id']][$laa['la_id']] = TRUE;
178    }
179  }
180  return $device_rules;
181}
182
183
184// DOCME needs phpdoc block
185// TESTME needs unit testing
186function process_syslog($line, $update)
187{
188  global $config;
189  global $rules;
190  global $device_rules;
191  global $maint;
192
193  $entry = process_syslog_line($line);
194
195  if ($entry === FALSE)
196  {
197    // Incorrect/filtered syslog entry
198    return FALSE;
199  }
200  else if ($entry['device_id'])
201  {
202    // Main syslog entry with detected device
203    if ($update)
204    {
205      $log_id = dbInsert(
206        array(
207          'device_id' => $entry['device_id'],
208          'host'      => $entry['host'],
209          'program'   => $entry['program'],
210          'facility'  => $entry['facility'],
211          'priority'  => $entry['priority'],
212          'level'     => $entry['level'],
213          'tag'       => $entry['tag'],
214          'msg'       => $entry['msg'],
215          'timestamp' => $entry['timestamp']
216        ),
217        'syslog'
218      );
219    }
220
221//$req_dump = print_r(array($entry, $rules, $device_rules), TRUE);
222//$fp = fopen('/tmp/syslog.log', 'a');
223//fwrite($fp, $req_dump);
224//fclose($fp);
225
226      // Add syslog alert into notification queue
227      $notification_type = 'syslog';
228
229      /// FIXME, I not know how 'syslog_rules_assoc' is filled, I pass rules to all devices
230      /// FIXME, this is copy-pasted from above, while not have WUI for syslog_rules_assoc
231      foreach ($rules as $la_id => $rule)
232      {
233        // Skip processing syslog rule if device rule not cached (see: cache_syslog_rules_assoc() )
234        if (!empty($device_rules) && !isset($device_rules[$entry['device_id']][$la_id]))
235        {
236          continue;
237        }
238
239        if (preg_match($rule['la_rule'], $entry['msg_orig'], $matches)) // Match syslog by rule pattern
240        {
241
242          // Mark no notification during maintenance
243          if (isset($maint['device'][$entry['device_id']]) || (isset($maint['global']) && $maint['global'] > 0))
244          {
245            $notified = '-1';
246          } else {
247            $notified = '0';
248          }
249
250          // Detect some common entities patterns in syslog message
251
252          $log_id = dbInsert(array('device_id' => $entry['device_id'],
253                                   'la_id'     => $la_id,
254                                   'syslog_id' => $log_id,
255                                   'timestamp' => $entry['timestamp'],
256                                   'program'   => $entry['program'],
257                                   'message'   => $entry['msg'], // Use cleared msg instead original (see process_syslog_line() tests)
258                                   'notified'  => $notified), 'syslog_alerts');
259
260          // Add notification to queue
261          if ($notified != '-1')
262          {
263            $alert_unixtime = strtotime($entry['timestamp']);
264
265            $device = device_by_id_cache($entry['device_id']);
266            $message_tags = array(
267                'ALERT_STATE'         => "SYSLOG",
268                'ALERT_URL'           => generate_url(array('page'        => 'device',
269                                                            'device'      => $device['device_id'],
270                                                            'tab'         => 'alert',
271                                                            'entity_type' => 'syslog')),
272
273                'ALERT_UNIXTIME'          => $alert_unixtime,                        // Standart unixtime
274                'ALERT_TIMESTAMP'         => $entry['timestamp'] . date(' P'),       //           ie: 2000-12-21 16:01:07 +02:00
275                'ALERT_TIMESTAMP_RFC2822' => date('r', $alert_unixtime),             // RFC 2822, ie: Thu, 21 Dec 2000 16:01:07 +0200
276                'ALERT_TIMESTAMP_RFC3339' => date(DATE_RFC3339, $alert_unixtime),    // RFC 3339, ie: 2005-08-15T15:52:01+00:00
277                'ALERT_ID'            => $la_id,
278                'ALERT_MESSAGE'       => $rule['la_descr'],
279                'CONDITIONS'          => $rule['la_rule'],
280                'METRICS'             => $entry['msg'],
281
282                // Syslog TAGs
283                'SYSLOG_RULE'         => $rule['la_rule'],
284                'SYSLOG_MESSAGE'      => $entry['msg'],
285                'SYSLOG_PROGRAM'      => $entry['program'],
286                'SYSLOG_TAG'          => $entry['tag'],
287                'SYSLOG_FACILITY'     => $entry['facility'],
288
289                // Device TAGs
290                'DEVICE_HOSTNAME'     => $device['hostname'],
291                'DEVICE_SYSNAME'      => $device['sysName'],
292                //'DEVICE_SYSDESCR'     => $device['sysDescr'],
293                'DEVICE_ID'           => $device['device_id'],
294                'DEVICE_LINK'         => generate_device_link($device, NULL, array('tab' => 'alerts', 'entity_type' => 'syslog')),
295                'DEVICE_HARDWARE'     => $device['hardware'],
296                'DEVICE_OS'           => $device['os_text'] . ' ' . $device['version'] . ($device['features'] ? ' (' . $device['features'] . ')' : ''),
297                //'DEVICE_TYPE'         => $device['type'],
298                'DEVICE_LOCATION'     => $device['location'],
299                'DEVICE_UPTIME'       => deviceUptime($device),
300                'DEVICE_REBOOTED'     => format_unixtime($device['last_rebooted']),
301            );
302            $message_tags['TITLE'] = alert_generate_subject($device, 'SYSLOG', $message_tags);
303
304              // Get contacts for $la_id
305              $contacts = get_alert_contacts($entry['device_id'], $la_id, $notification_type);
306
307              foreach($contacts AS $contact) {
308
309                $notification = array(
310                    'device_id'             => $entry['device_id'],
311                    'log_id'                => $log_id,
312                    'aca_type'              => $notification_type,
313                    'severity'              => $entry['priority'],
314                    'endpoints'             => json_encode($contact),
315                    //'message_graphs'        => $message_tags['ENTITY_GRAPHS_ARRAY'],
316                    'notification_added'    => time(),
317                    'notification_lifetime' => 300,                   // Lifetime in seconds
318                    'notification_entry'    => json_encode($entry),   // Store full alert entry for use later if required (not sure that this needed)
319                );
320                //unset($message_tags['ENTITY_GRAPHS_ARRAY']);
321                $notification['message_tags'] = json_encode($message_tags);
322                $notification_id = dbInsert($notification, 'notifications_queue');
323            } // End foreach($contacts)
324          } // End if($notified)
325        }  // End if syslog rule matches
326      } // End foreach($rules)
327
328    unset($os);
329  }
330  else if ($config['syslog']['unknown_hosts'])
331  {
332    // EXPERIMENTAL. Host not known, currently not used.
333    if ($update)
334    {
335      // Store entries for unknown hosts with NULL device_id
336      $log_id = dbInsert(
337        array(
338          //'device_id' => $entry['device_id'], // Default is NULL
339          'host'      => $entry['host'],
340          'program'   => $entry['program'],
341          'facility'  => $entry['facility'],
342          'priority'  => $entry['priority'],
343          'level'     => $entry['level'],
344          'tag'       => $entry['tag'],
345          'msg'       => $entry['msg'],
346          'timestamp' => $entry['timestamp']
347        ),
348        'syslog'
349      );
350      //var_dump($entry);
351    }
352  }
353
354  return $entry;
355}
356
357/**
358 * Process syslog line. Convert raw syslog line (with observium format) into array.
359 * Also rewrite some entries by device os.
360 *
361 * Observium template:
362 * host||facility||priority||level||tag||timestamp||msg||program
363 *
364 * @param string $line Raw syslog line by observium template
365 * @return array|false Array with processed syslog entry, or FALSE if incorrect/filtered msg.
366 */
367function process_syslog_line($line)
368{
369  global $config;
370
371  // Compatability with old param as array
372  if (is_array($line)) { return $line; }
373
374  $entry = array(); // Init
375
376  $entry_array = explode('||', trim($line));
377  $entry['host']      = array_shift($entry_array);
378  $entry['facility']  = array_shift($entry_array);
379  $entry['priority']  = array_shift($entry_array);
380  $entry['level']     = array_shift($entry_array);
381  $entry['tag']       = array_shift($entry_array);
382  $entry['timestamp'] = array_shift($entry_array);
383  if (count($entry_array) > 2)
384  {
385    // Some time message have || inside:
386    // 127.0.0.1||9||6||6||CRON[3196]:||2018-03-13 06:25:01|| (root) CMD (test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ))||CRON
387    $entry['program'] = array_pop($entry_array);
388    $entry['msg']     = implode('||', $entry_array); // reimplode msg string with "||" inside
389  } else {
390    $entry['msg']     = array_shift($entry_array);
391    $entry['program'] = array_shift($entry_array);
392  }
393
394  // Filter by msg string
395  if (str_contains($entry['msg'], $config['syslog']['filter']))
396  {
397    return FALSE;
398  }
399  //foreach ($config['syslog']['filter'] as $bi)
400  //{
401  //  if (strpos($entry['msg'], $bi) !== FALSE)
402  //  {
403  //    //echo('D-'.$bi);
404  //    return FALSE;
405  //  }
406  //}
407
408  $entry['msg_orig'] = $entry['msg'];
409
410  // Initial rewrites
411  $entry['host']      = strtolower(trim($entry['host']));
412
413  if (isset($config['syslog']['debug']) && $config['syslog']['debug'] &&
414      !defined('__PHPUNIT_PHAR__')) // Skip on Unit tests
415  {
416    // Store RAW syslog line into debug.log
417    logfile('debug.'.$entry['host'].'.syslog', $line);
418  }
419
420  // Rewrite priority/level/facility from strings to numbers
421  $entry['priority']  = priority_string_to_numeric($entry['priority']);
422  $entry['level']     = priority_string_to_numeric($entry['level']);
423  if (isset($config['syslog']['facilities'][$entry['facility']]))
424  {
425    // Convert numeric facility to string
426    $entry['facility'] = $config['syslog']['facilities'][$entry['facility']]['name'];
427  }
428  //$entry['facility']  = facility_string_to_numeric($entry['facility']);
429
430  $entry['device_id'] = get_cache($entry['host'], 'device_id');
431  //print_vars($entry);
432  //print_vars($GLOBALS['dev_cache']);
433  if ($entry['device_id'])
434  {
435    // Process msg/program for known os/os_group
436    $os       = get_cache($entry['host'], 'os');
437    $os_group = get_cache($entry['host'], 'os_group');
438
439    // Detect message repeated
440    if (strpos($entry['msg'], 'repeated ') !== FALSE && preg_match('/repeated \d+ times(?:\:\ +\[\s*(?<msg>.+)\])?\s*$/', $entry['msg'], $matches))
441    {
442      //var_dump($matches);
443      if (isset($matches['msg']))
444      {
445        $entry['msg'] = $matches['msg'];
446      } else {
447        // Always skip unusefull entries 'message repeated X times' (without any message)
448        return FALSE;
449      }
450    }
451
452    // OS definition based syslog msg format
453    if (isset($config['os'][$os]['syslog_msg']))
454    {
455      foreach ($config['os'][$os]['syslog_msg'] as $pattern)
456      {
457        if (preg_match($pattern, $entry['msg'], $matches))
458        {
459          if (OBS_DEBUG)
460          {
461            print_cli_table(array(array('syslog msg', $entry['msg']), array('matched pattern', $pattern)), NULL);
462          }
463          // Override founded msg/tag/program references
464          if (isset($matches['msg']))     { $entry['msg']     = $matches['msg']; }
465          if (isset($matches['program'])) { $entry['program'] = $matches['program']; }
466          if (isset($matches['tag']))     { $entry['tag']     = $matches['tag']; }
467          // Tags, also allowed multiple tagsX (0-9), started from 0
468          $i = 0;
469          while (isset($matches['tag'.$i]) && $matches['tag'.$i])
470          {
471            $entry['tag']  = rtrim($entry['tag'], ':'); // remove last :
472            $entry['tag'] .= ','.$matches['tag'.$i];
473            $i++;
474          }
475          break; // Stop other loop if pattern found
476        }
477      }
478    }
479    // OS definition based syslog program format
480    if (isset($config['os'][$os]['syslog_program']) && strlen($entry['program']))
481    {
482      foreach ($config['os'][$os]['syslog_program'] as $pattern)
483      {
484        if (preg_match($pattern, $entry['program'], $matches))
485        {
486          if (OBS_DEBUG)
487          {
488            print_cli_table(array(array('syslog program', $entry['program']), array('matched pattern', $pattern)), NULL);
489          }
490          // Override founded tag/program references
491          if (isset($matches['program'])) { $entry['program'] = $matches['program']; }
492          if (isset($matches['tag']))     { $entry['tag']     = $matches['tag']; }
493          /*
494          // Tags, also allowed multiple tagsX (0-9), started from 0
495          $i = 0;
496          while (isset($matches['tag'.$i]) && $matches['tag'.$i])
497          {
498            $entry['tag']  = rtrim($entry['tag'], ':'); // remove last :
499            $entry['tag'] .= ','.$matches['tag'.$i];
500            $i++;
501          }
502          */
503          break; // Stop other loop if pattern found
504        }
505      }
506    }
507
508    // Additional syslog cases, when regex from definition not possible
509    if ($os_group == 'cisco')
510    {
511      // Cisco by default store in tag/program syslog fields just seq no,
512      // this not useful for this fields
513      if ($entry['priority'] > 6 && (is_numeric($entry['program']) || empty($entry['program'])))
514      {
515        $entry['program'] = 'debug';
516        $entry['tag']     = 'debug';
517        // Remove prior seqno and timestamp from msg
518        $entry['msg'] = preg_replace('/^\s*(?<seq>\d+:)*\s*(?<timestamp>.*?\d+\:\d+\:\d+(?:\.\d+)?(?:\ [\w\-\+]+)?): /', '', $entry['msg']);
519      }
520    }
521    //  CLEANME. MOVED to cisco group definition
522    //  //NOTE. Please include examples for syslog entries, to know why need some preg_replace()
523    //  if (strstr($entry['msg'], '%'))
524    //  {
525    //    //10.0.0.210||23||4||4||26644:||2013-11-08 07:19:24|| 033884: Nov  8 07:19:23.993: %FW-4-TCP_OoO_SEG: Dropping TCP Segment: seq:-1169729434 1500 bytes is out-of-order; expected seq:3124765814. Reason: TCP reassembly queue overflow - session 10.10.32.37:56316 to 93.186.239.142:80 on zone-pair Local->Internet class All_Inspection||26644
526    //    //hostname||17||5||5||192462650:||2014-06-17 11:16:01|| %SSH-5-SSH2_SESSION: SSH2 Session request from 10.95.0.42 (tty = 0) using crypto cipher 'aes256-cbc', hmac 'hmac-sha1' Succeeded||192462650
527    //    if (strpos($entry['msg'], ': %'))
528    //    {
529    //      list(,$entry['msg']) = explode(': %', $entry['msg'], 2);
530    //      $entry['msg'] = "%" . $entry['msg'];
531    //    }
532    //    $entry['msg'] = preg_replace("/^%(.+?):\ /", "\\1||", $entry['msg']);
533    //  } else {
534    //    $entry['msg'] = preg_replace("/^.*[0-9]:/", "", $entry['msg']);
535    //    $entry['msg'] = preg_replace("/^[0-9][0-9]\ [A-Z]{3}:/", "", $entry['msg']);
536    //    $entry['msg'] = preg_replace("/^(.+?):\ /", "\\1||", $entry['msg']);
537    //  }
538    //  //$entry['msg'] = preg_replace("/^.+\.[0-9]{3}:/", "", $entry['msg']); /// FIXME. Show which entries this should replace. It's broke all entries with 'IP:PORT'.
539    //  $entry['msg'] = preg_replace("/^.+-Traceback=/", "Traceback||", $entry['msg']);
540    //
541    //  list($entry['program'], $entry['msg']) = explode("||", $entry['msg'], 2);
542    //  $entry['msg'] = preg_replace("/^[0-9]+:/", "", $entry['msg']);
543    //
544    //  if (!$entry['program'])
545    //  {
546    //     $entry['msg'] = preg_replace("/^([0-9A-Z\-]+?):\ /", "\\1||", $entry['msg']);
547    //     list($entry['program'], $entry['msg']) = explode("||", $entry['msg'], 2);
548    //  }
549    //
550    //  if (!$entry['msg']) { $entry['msg'] = $entry['program']; unset ($entry['program']); }
551    //}
552    // CLEANME. MOVED to os definition
553    //else if ($os == 'iosxr')
554    //{
555    //  //1.1.1.1||23||5||5||920:||2014-11-26 17:29:48||RP/0/RSP0/CPU0:Nov 26 16:29:48.161 : bgp[1046]: %ROUTING-BGP-5-ADJCHANGE : neighbor 1.1.1.2 Up (VRF: default) (AS: 11111) ||920
556    //  //1.1.1.2||23||6||6||253:||2014-11-26 17:30:21||RP/0/RSP0/CPU0:Nov 26 16:30:21.710 : SSHD_[65755]: %SECURITY-SSHD-6-INFO_GENERAL : Client closes socket connection ||253
557    //  //1.1.1.3||local0||err||err||83||2015-01-14 07:29:45||oly-er-01 LC/0/0/CPU0:Jan 14 07:29:45.556 CET: pfilter_ea[301]: %L2-PFILTER_EA-3-ERR_IM_CAPS : uidb set  acl failed on interface Bundle-Ether1.1501.ip43696. (null) ||94795
558    //
559    //  // This also provides two unused entries containing the actual process on IOS-XE as well as the module which originated the message
560    //
561    //  if (preg_match('/%(?<program>\S+) :(?<msg>.+)/', $entry['msg'], $matches))
562    //  {
563    //    $entry['program'] = $matches['program'];
564    //    $entry['msg'] = $matches['msg'];
565    //  }
566    //  //list($entry['temp_mod'], $entry['temp_prog'], $entry['msg']) = explode(': ', $entry['msg'], 3);
567    //  //list($entry['temp_mod'],) = explode(":", $entry['temp_mod']);
568    //  //list($entry['program'], $entry['msg']) = explode(' : ', $entry['msg'], 2);
569    //}
570    else if (in_array($os, array('junos', 'junose')))
571    {
572      //1.1.1.1||9||6||6||/usr/sbin/cron[1305]:||2015-04-08 14:30:01|| (root) CMD (   /usr/libexec/atrun)||
573      if (str_contains($entry['tag'], '/'))
574      {
575        $entry['tag']     = end(explode('/', $entry['tag'])); // /usr/sbin/cron[1305]: -> cron[1305]:
576      }
577      if (empty($entry['program']))
578      {
579        list($entry['program']) = explode('[', rtrim($entry['tag'], ':')); // cron[1305]: -> cron
580      }
581      //1.1.1.1||3||4||4||mib2d[1230]:||2015-04-08 14:30:11|| SNMP_TRAP_LINK_DOWN: ifIndex 602, ifAdminStatus up(1), ifOperStatus down(2), ifName ge-0/1/0||mib2d
582      //1.1.1.1||3||6||6||chassism[1210]:||2015-04-08 14:30:16|| ethswitch_eth_devstop: called for port ge-0/1/1||chassism
583      //1.1.1.1||3||3||3||chassism[1210]:||2015-04-08 14:30:22|| ETH:if_ethgetinfo() returns error||chassism
584    }
585    else if ($os == 'linux' && get_cache($entry['host'], 'version') == 'Point')
586    {
587      // Cisco WAP200 and similar
588      $matches = array();
589      if (preg_match('#Log: \[(?P<program>.*)\] - (?P<msg>.*)#', $entry['msg'], $matches))
590      {
591        $entry['msg']     = $matches['msg'];
592        $entry['program'] = $matches['program'];
593      }
594      unset($matches);
595
596    }
597    else if ($os_group == 'unix')
598    {
599      //1.1.1.1||9||6||6||/usr/sbin/cron[1305]:||2015-04-08 14:30:01|| (root) CMD (   /usr/libexec/atrun)||
600      if (str_contains($entry['tag'], '/'))
601      {
602        $entry['tag']     = end(explode('/', $entry['tag'])); // /usr/sbin/cron[1305]: -> cron[1305]:
603        // And same for program if it based on tag (from os definitions)
604        if (str_contains($entry['program'], '/'))
605        {
606          $entry['program'] = end(explode('/', $entry['program']));
607        }
608      }
609      if (empty($entry['program']))
610      {
611        list($entry['program']) = explode('[', rtrim($entry['tag'], ':')); // cron[1305]: -> cron
612      }
613
614      // User_CommonName/123.213.132.231:39872 VERIFY OK: depth=1, /C=PL/ST=Malopolska/O=VLO/CN=v-lo.krakow.pl/emailAddress=root@v-lo.krakow.pl
615      if ($entry['facility'] == 'daemon' && preg_match('#/([0-9]{1,3}\.) {3}[0-9]{1,3}:[0-9]{4,} ([A-Z]([A-Za-z])+( ?)) {2,}:#', $entry['msg']))
616      {
617        $entry['program'] = 'OpenVPN';
618      }
619      // pop3-login: Login: user=<username>, method=PLAIN, rip=123.213.132.231, lip=123.213.132.231, TLS
620      // POP3(username): Disconnected: Logged out top=0/0, retr=0/0, del=0/1, size=2802
621      else if ($entry['facility'] == 'mail' && preg_match('/^(((pop3|imap)\-login)|((POP3|IMAP)\(.*\))):/', $entry['msg']))
622      {
623        $entry['program'] = 'Dovecot';
624      }
625      // CLEANME. MOVED to unix group definition
626      // pam_krb5(sshd:auth): authentication failure; logname=root uid=0 euid=0 tty=ssh ruser= rhost=123.213.132.231
627      // pam_krb5[sshd:auth]: authentication failure; logname=root uid=0 euid=0 tty=ssh ruser= rhost=123.213.132.231
628      //else if (preg_match('/^(?P<program>(\S((\(|\[).*(\)|\])))):(?P<msg>.*)$/', $entry['msg'], $matches))
629      //{
630      //  $entry['msg']     = $matches['msg'];
631      //  $entry['program'] = $matches['program'];
632      //}
633      // pam_krb5: authentication failure; logname=root uid=0 euid=0 tty=ssh ruser= rhost=123.213.132.231
634      // diskio.c: don't know how to handle 10 request
635      // MOVED to unix group definition
636      //else if (preg_match('/^(?P<program>[^\s\(\[]*):\ (?P<msg>.*)$/', $entry['msg'], $matches))
637      //{
638      //  $entry['msg']     = $matches['msg'];
639      //  $entry['program'] = $matches['program'];
640      //}
641      //// Wed Mar 26 12:54:17 2014 : Auth: Login incorrect (mschap: External script says Logon failure (0xc000006d)): [username] (from client 10.100.1.3 port 0 cli a4c3612a4077 via TLS tunnel)
642      //else if (!empty($entry['program']) && preg_match('/^.*:\ '.$entry['program'].':\ (?P<msg>[^(]+\((?P<tag>[^:]+):.*)$/', $entry['msg'], $matches))
643      //{
644      //  $entry['msg']     = $matches['msg'];
645      //  $entry['tag'] = $matches['tag'];
646      //}
647      // SYSLOG CONNECTION BROKEN; FD='6', SERVER='AF_INET(123.213.132.231:514)', time_reopen='60'
648      // 1.1.1.1||5||3||3||rsyslogd-2039:||2016-10-06 23:03:27|| Could no open output pipe '/dev/xconsole': No such file or directory [try http://www.rsyslog.com/e/2039 ]||rsyslogd-2039
649      $entry['program'] = preg_replace('/\-\d+$/', '', $entry['program']);
650      $entry['program'] = str_replace('rsyslogd0', 'rsyslogd', $entry['program']);
651      unset($matches);
652      if (str_contains($entry['program'], '/'))
653      {
654        // postfix/smtp
655        list($entry['program'], $tag) = explode('/', $entry['program'], 2);
656        $entry['tag'] .= ','.$tag;
657      }
658    }
659    // CLEANME. Moved to os definition
660    //else if ($os == 'ftos')
661    //{
662    //  if (empty($entry['program']))
663    //  {
664    //    //1.1.1.1||23||5||5||||2014-11-23 21:48:10|| Nov 23 21:48:10.745: hostname: %STKUNIT0-M:CP %SEC-5-LOGOUT: Exec session is terminated for user rancid on line vty0||
665    //    list(,, $entry['program'], $entry['msg']) = explode(': ', $entry['msg'], 4);
666    //    list(, $entry['program']) = explode(' %', $entry['program'], 2);
667    //  }
668    //  //Jun 3 02:33:23.489: %STKUNIT0-M:CP %SNMP-3-SNMP_AUTH_FAIL: SNMP Authentication failure for SNMP request from host 176.10.35.241
669    //  //Jun 1 17:11:50.806: %STKUNIT0-M:CP %ARPMGR-2-MAC_CHANGE: IP-4-ADDRMOVE: IP address 11.222.30.53 is moved from MAC address 52:54:00:7b:37:ad to MAC address 52:54:00:e4:ec:06 .
670    //  //if (strpos($entry['msg'], '%STKUNIT') === 0)
671    //  //{
672    //  //  list(, $entry['program'], $entry['msg']) = explode(': ', $entry['msg'], 3);
673    //  //  //$entry['timestamp'] = date("Y-m-d H:i:s", strtotime($entry['timestamp'])); // convert to timestamp
674    //  //  list(, $entry['program']) = explode(' %', $entry['program'], 2);
675    //  //}
676    //}
677    else if ($os == 'netscaler')
678    {
679      //10/03/2013:16:49:07 GMT dk-lb001a PPE-4 : UI CMD_EXECUTED 10367926 : User so_readonly - Remote_ip 10.70.66.56 - Command "stat lb vserver" - Status "Success"
680      list(,,,$entry['msg']) = explode(' ', $entry['msg'], 4);
681      list($entry['program'], $entry['msg']) = explode(' : ', $entry['msg'], 3);
682    }
683    else if (str_starts($entry['tag'], '('))
684    {
685      // Ubiquiti Unifi devices
686      // Wtf is BZ2LR and BZ@..
687      /**
688       *Old:  10.10.34.10||3||6||6||hostapd:||2014-07-18 11:29:35|| ath2: STA c8:dd:c9:d1:d4:aa IEEE 802.11: associated||hostapd
689       *New:  10.10.34.10||3||6||6||(BZ2LR,00272250c1cd,v3.2.5.2791)||2014-12-12 09:36:39|| hostapd: ath2: STA dc:a9:71:1b:d6:c7 IEEE 802.11: associated||(BZ2LR,00272250c1cd,v3.2.5.2791)
690       *New2: 10.10.34.11||1||6||6||("BZ2LR,00272250c119,v3.7.8.5016")||2016-10-06 18:20:25|| syslog: wevent.ubnt_custom_event(): EVENT_STA_LEAVE ath0: dc:a9:71:1b:d6:c7 / 3||("BZ2LR,00272250c119,v3.7.8.5016")
691       *      10.10.34.7||1||6||6||("U7LR,44d9e7f618f2,v3.7.17.5220")||2016-10-06 18:21:22|| libubnt[16915]: wevent.ubnt_custom_event(): EVENT_STA_JOIN ath0: fc:64:ba:c1:7d:28 / 1||("U7LR,44d9e7f618f2,v3.7.17.5220")
692       */
693      if (preg_match('/^\s*(?<tag>(?<program>\S+?)(\[\d+\])?): +(?<msg>.*)/', $entry['msg'], $matches))
694      {
695        $entry['msg']     = $matches['msg'];
696        $entry['program'] = $matches['program'];
697        $entry['tag']     = $matches['tag'];
698      }
699
700    }
701    else if (str_contains($entry['program'], ','))
702    {
703      // Microtik (and some other)
704      // mikrotik||user||5||notice||0d||2018-03-23 07:48:39||dhcp105 assigned 192.168.58.84 to 80:BE:05:7A:73:6E||dhcp,info
705      list($entry['program'], $entry['tag']) = explode(',', $entry['program'], 2);
706    }
707
708    // Always clear timestamp from beginig of message (if still leaved), test strings:
709    //2018-10-16T18:13:03+02:00 hostname
710    $pettern_timestamp_rfc3339 = '/^\s*\*?(?<year>[0-9]{4})\-(?<month>[0-9]{2})\-(?<day>[0-9]{2})(?:[Tt](?<hour>[0-9]{2}):(?<minute>[0-9]{2}):(?<second>(?:[0-9]{2})(?:\.[0-9]+)?)?)(?<tz>(?:[Zz]|[+\-](?:[0-9]{2}):(?:[0-9]{2})))?/';
711    //Wed Mar 26 12:54:17 2014 :
712    //May 30 15:33:20.636 UTC :
713    //May 30 15:33:20.636 2014 UTC :
714    //Mar 19 06:48:12.692:
715    //Mar 19 15:12:23 MSK/MSD:
716    //Apr 24 2013 16:00:28 INT-FW01 :
717    //LC/0/0/CPU0:Oct 19 09:17:07.433 :
718    //oly-er-01 LC/0/0/CPU0:Jan 14 07:29:45.556 CET:
719    //003174: Jan 26 04:27:09.174 MSK:
720    //001743: *Apr 25 04:16:54.749:
721    //033884: Nov  8 07:19:23.993:
722    // Should be false:
723    //CompDHCP assigned 10.0.0.222 to 4C:32:75:90:69:33
724    $pattern_timestamp = '/^(?<max2words>\s*(?:\S+\s+)?\S+?:)?\s*\*?(?<wmd>(?<week>[a-z]{3,} +)?(?<month>[a-z]{3,} +)(?<day>\d{1,2} +)(?<year0>[12]\d{3} +)?)?(?<hms>\d{1,2}\:\d{1,2}\:\d{1,2}(?:\.\d+)?)(?<year>\s+[12]\d{3})?(?<tz>\s+[a-z][\w\/\-]+)?\s*:\s/i';
725    // without TZ, example:
726    //Mar 21 13:07:05 netflow syslogd:
727    $pattern_timestamp_wo_tz = '/^\s*\*?(?<wmd>(?<week>[a-z]{3,} +)?(?<month>[a-z]{3,} +)(?<date>\d{1,2} +)(?<year0>[12]\d{3} +)?)?(?<hms>\d{1,2}\:\d{1,2}\:\d{1,2}(?:\.\d+)?)(?<year>\s+[12]\d{3})?/i';
728    $entry['msg']      = preg_replace(array($pattern_timestamp, $pattern_timestamp_wo_tz, $pettern_timestamp_rfc3339), '', $entry['msg']);
729
730    if (!strlen($entry['msg']))
731    {
732      // Something wrong, msg empty
733      return FALSE;
734    }
735
736    // Wed Mar 26 12:54:17 2014 : Auth: Login incorrect (mschap: External script says Logon failure (0xc000006d)): [username] (from client 10.100.1.3 port 0 cli a4c3612a4077 via TLS tunnel)
737    if (strlen($entry['program']))
738    {
739      // Always clear program from begining of message, ie Auth:, blabla[27346]:
740      $pattern_program = '/^\s*'.preg_quote($entry['program'], '/').'(\[\d+\])?\s*:/i';
741      $entry['msg']    = preg_replace($pattern_program, '', $entry['msg']);
742    }
743    else if (strlen($entry['facility']))
744    {
745      // fallback, better than nothing...
746      $entry['program'] = $entry['facility'];
747    } else {
748      $entry['program'] = 'generic'; // Derp, do not leave empty program
749    }
750
751    // Last point clear
752    $entry['program'] = strtoupper($entry['program']);
753    $entry['tag']     = trim($entry['tag'], ',: ');
754
755  }
756
757  //array_walk($entry, 'trim');
758  return array_map('trim', $entry);
759}
760
761// EOF
762