########################################################################################################################## # $Id: 93_Log2Syslog.pm 19029 2019-03-25 20:01:23Z DS_Starter $ ########################################################################################################################## # 93_Log2Syslog.pm # # (c) 2017-2019 by Heiko Maaz # e-mail: Heiko dot Maaz at t-online dot de # # This script is part of fhem. # # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Fhem is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # # The module is based on idea and input from betateilchen 92_rsyslog.pm # # Implements the Syslog Protocol of RFC 5424 https://tools.ietf.org/html/rfc5424 # and RFC 3164 https://tools.ietf.org/html/rfc3164 and # TLS Transport according to RFC5425 https://tools.ietf.org/pdf/rfc5425.pdf as well # ########################################################################################################################## package main; use strict; use warnings; use TcpServerUtils; use Scalar::Util qw(looks_like_number); use Encode qw(encode_utf8); eval "use IO::Socket::INET;1" or my $MissModulSocket = "IO::Socket::INET"; eval "use Net::Domain qw(hostname hostfqdn hostdomain domainname);1" or my $MissModulNDom = "Net::Domain"; eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; # Versions History intern: our %Log2Syslog_vNotesIntern = ( "5.8.1" => "23.07.2019 attribute waitForEOF rename to useEOF, useEOF also for type sender ", "5.8.0" => "20.07.2019 attribute waitForEOF, solution for Forum: https://forum.fhem.de/index.php/topic,75426.msg958836.html#msg958836 ", "5.7.0" => "20.07.2019 change logging and chomp received data, use raw parse format if automatic mode don't detect a valid format, ". "change getifdata tcp stack error handling (if sysread undef)", "5.6.5" => "19.07.2019 bugfix parse BSD if ID (TAG) is used, function DbLog_splitFn -> Log2Syslog_DbLogSplit, new attribute useParsefilter ", "5.6.4" => "19.07.2019 minor changes and fixes (max. lenth read to 16384, code && logging) ", "5.6.3" => "18.07.2019 fix state reading if changed disabled attribute ", "5.6.2" => "17.07.2019 Forum: https://forum.fhem.de/index.php/topic,75426.msg958836.html#msg958836 first try", "5.6.1" => "24.03.2019 prevent module from deactivation in case of unavailable Meta.pm ", "5.6.0" => "23.03.2019 attribute exclErrCond to exclude events from rating as \"error\" ", "5.5.0" => "18.03.2019 prepare for Meta.pm ", "5.4.0" => "17.03.2019 new feature parseProfile = Automatic ", "5.3.2" => "08.02.2019 fix version numbering ", "5.3.1" => "21.10.2018 get of FQDN changed ", "5.3.0" => "16.10.2018 attribute sslCertPrefix added (Forum:#92030), module hints & release info order switched ", "5.2.1" => "08.10.2018 setpayload of BSD-format changed, commandref revised ", "5.2.0" => "02.10.2018 added direct help for attributes", "5.1.0" => "01.10.2018 new get versionNotes command", "5.0.1" => "27.09.2018 Log2Syslog_closesock if write error:.* , delete readings code changed", "5.0.0" => "26.09.2018 TCP-Server in Collector-mode, HIPCACHE added, PROFILE as Internal, Parse_Err_No as reading, octetCount attribute, TCP-SSL-support, set 'reopen' command, code fixes", "4.8.5" => "20.08.2018 BSD/parseFn parsing changed, BSD setpayload changed, new variable \$IGNORE in parseFn", "4.8.4" => "15.08.2018 BSD parsing changed", "4.8.3" => "14.08.2018 BSD setpayload changed, BSD parsing changed, Internal MYFQDN", "4.8.2" => "13.08.2018 rename makeMsgEvent to makeEvent", "4.8.1" => "12.08.2018 IETF-Syslog without VERSION changed, Log verbose 1 to 2 changed in parsePayload", "4.8.0" => "12.08.2018 enhanced IETF Parser to match logs without version", "4.7.0" => "10.08.2018 Parser for TPLink", "4.6.1" => "10.08.2018 some perl warnings, changed IETF Parser", "4.6.0" => "08.08.2018 set sendTestMessage added, Attribute 'contDelimiter', 'respectSeverity'", "4.5.1" => "07.08.2018 BSD Regex changed, setpayload of BSD changed", "4.5.0" => "06.08.2018 Regex capture groups used in parsePayload to set variables, parsing of BSD changed, Attribute 'makeMsgEvent' added", "4.4.0" => "04.08.2018 Attribute 'outputFields' added", "4.3.0" => "03.08.2018 Attribute 'parseFn' added", "4.2.0" => "03.08.2018 evaluate sender peer ip-address/hostname, use it as reading in event generation", "4.1.0" => "02.08.2018 state event generation changed", "4.0.0" => "30.07.2018 server mode (Collector)", "3.2.1" => "04.05.2018 fix compatibility with newer IO::Socket::SSL on debian 9, attr ssldebug for debugging SSL messages", "3.2.0" => "22.11.2017 add NOTIFYDEV if possible", "3.1.0" => "28.08.2017 get-function added, commandref revised, \$readingFnAttributes deleted", "3.0.0" => "27.08.2017 change attr type to protocol, ready to check in", "2.6.0" => "26.08.2017 more than one Log2Syslog device can be created", "2.5.2" => "26.08.2018 fix in splitting timestamp, change Log2Syslog_trate using internaltimer with attr rateCalcRerun, function Log2Syslog_closesock", "2.5.1" => "24.08.2017 some fixes", "2.5.0" => "23.08.2017 TLS encryption available, new readings, \$readingFnAttributes", "2.4.1" => "21.08.2017 changes in sub Log2Syslog_charfilter, change PROCID to \$hash->{SEQNO} switch to non-blocking in subs event/Log2Syslog_fhemlog", "2.4.0" => "20.08.2017 new sub Log2Syslog_Log3slog for entries in local fhemlog only -> verbose support", "2.3.1" => "19.08.2017 commandref revised", "2.3.0" => "18.08.2017 new parameter 'ident' in DEF, sub setidex, Log2Syslog_charfilter", "2.2.0" => "17.08.2017 set BSD data length, set only acceptable characters (USASCII) in payload, commandref revised", "2.1.0" => "17.08.2017 sub Log2Syslog_opensock created", "2.0.0" => "16.08.2017 create syslog without SYS::SYSLOG", "1.1.1" => "13.08.2017 registrate Log2Syslog_fhemlog to %loginform in case of sending fhem-log attribute timeout, commandref revised", "1.1.0" => "26.07.2017 add regex search to sub Log2Syslog_fhemlog", "1.0.0" => "25.07.2017 initial version" ); # Versions History extern: our %Log2Syslog_vNotesExtern = ( "5.8.1" => "23.07.2019 New attribute \"useParsefilter\" to remove other characters than ASCII from payload before parse it. ". "New attribute \"useEOF\" to parse not till the sender was sending an EOF signal (Collector), or in ". "case of model Sender, after transmission an EOF signal is send. A bugfix for ". "parsing BSD if the ID (TAG) is used was implemented. Minor other fixes and changes. ", "5.6.0" => "23.03.2019 New attribute \"exclErrCond\" to exclude events from rating as \"Error\" even though the ". "event contains the text \"Error\". ", "5.4.0" => "17.03.2019 New feature parseProfile = Automatic. The module may detect the message format BSD or IETF automatically in server mode ", "5.3.2" => "08.02.2019 fix version numbering ", "5.3.0" => "16.10.2018 attribute sslCertPrefix added to support multiple SSL-keys (Forum:#92030)", "5.2.1" => "08.10.2018 Send format of BSD changed. The TAG-field was changed to \"IDENT[PID]: \" ", "5.2.0" => "02.10.2018 direct help for attributes added", "5.1.0" => "29.09.2018 new get <name> versionNotes command ", "5.0.1" => "27.09.2018 automatic reconnect to syslog-server in case of write error ", "5.0.0" => "26.09.2018 Some changes:
  • TCP Server mode is possible now for Collector devices<\li>
  • the used parse-profile is shown as Internal<\li>
  • Parse_Err_No counts faulty persings since start<\li>
  • new octetCount attribute switches the syslog framing method (see also RFC6587 Transmission of Syslog Messages over TCP)<\li>
  • TCP SSL-support<\li>
  • new set 'reopen' command to reconnect a broken connection<\li>
  • some code fixes ", "4.8.5" => "20.08.2018 BSD/parseFn parse changed, BSD setpayload changed, new variable \$IGNORE in parseFn ", "4.8.4" => "15.08.2018 BSD parse changed again ", "4.8.3" => "14.08.2018 BSD setpayload changed, BSD parse changed, new Internal MYFQDN ", "4.8.2" => "13.08.2018 rename makeMsgEvent to makeEvent ", "4.8.1" => "12.08.2018 IETF-Syslog without VERSION changed, Log verbose 1 to 2 changed in parsePayload ", "4.8.0" => "12.08.2018 enhanced IETF Parser to match logs without version ", "4.7.0" => "10.08.2018 Parser for TPLink added ", "4.6.1" => "10.08.2018 fix some perl warnings, changed IETF Parser ", "4.6.0" => "08.08.2018 set sendTestMessage added, new attributes 'contDelimiter', 'respectSeverity' ", "4.5.1" => "07.08.2018 BSD Regex changed, setpayload of BSD changed ", "4.5.0" => "06.08.2018 parsing of BSD changed, attribute 'makeMsgEvent' added ", "4.4.0" => "04.08.2018 Attribute 'outputFields' added ", "4.3.0" => "03.08.2018 Attribute 'parseFn' added ", "4.2.0" => "03.08.2018 evaluate sender peer ip-address/hostname and use it as reading in event generation ", "4.1.0" => "02.08.2018 state event generation changed ", "4.0.0" => "30.07.2018 Server mode (Collector) implemented ", "3.2.1" => "04.05.2018 fix compatibility with newer IO::Socket::SSL on debian 9, attribute ssldebug for debugging SSL messages ", "3.2.0" => "22.11.2017 add NOTIFYDEV if possible ", "3.1.0" => "28.08.2017 get-function added, commandref revised ", "3.0.0" => "27.08.2017 change attr type to protocol, ready to first check in ", "2.6.0" => "26.08.2017 more than one Log2Syslog device can be created ", "2.5.2" => "26.08.2018 attribute rateCalcRerun ", "2.5.1" => "24.08.2017 some bugfixes ", "2.5.0" => "23.08.2017 TLS encryption available to Sender ", "2.4.1" => "21.08.2017 change PROCID to \$hash->{SEQNO}, switch to non-blocking in subs event/fhemlog ", "2.4.0" => "20.08.2017 new sub for entries in local fhemlog only including verbose support ", "2.3.1" => "19.08.2017 commandref revised ", "2.3.0" => "18.08.2017 new parameter 'ident' in Define to indentify sylog source ", "2.2.0" => "17.08.2017 set BSD data length, set only acceptable characters (USASCII) in payload ", "2.0.0" => "16.08.2017 create syslog without perl module SYS::SYSLOG ", "1.1.0" => "26.07.2017 add regex search to sub Log2Syslog_fhemlog ", "1.0.0" => "25.07.2017 initial version " ); # Mappinghash BSD-Formatierung Monat our %Log2Syslog_BSDMonth = ( "01" => "Jan", "02" => "Feb", "03" => "Mar", "04" => "Apr", "05" => "May", "06" => "Jun", "07" => "Jul", "08" => "Aug", "09" => "Sep", "10" => "Oct", "11" => "Nov", "12" => "Dec", "Jan" => "01", "Feb" => "02", "Mar" => "03", "Apr" => "04", "May" => "05", "Jun" => "06", "Jul" => "07", "Aug" => "08", "Sep" => "09", "Oct" => "10", "Nov" => "11", "Dec" => "12" ); # Mappinghash Severity my %Log2Syslog_Severity = ( "0" => "Emergency", "1" => "Alert", "2" => "Critical", "3" => "Error", "4" => "Warning", "5" => "Notice", "6" => "Informational", "7" => "Debug", "Emergency" => "0", "Alert" => "1", "Critical" => "2", "Error" => "3", "Warning" => "4", "Notice" => "5", "Informational" => "6", "Debug" => "7" ); # Mappinghash Facility my %Log2Syslog_Facility = ( "0" => "kernel", "1" => "user", "2" => "mail", "3" => "system", "4" => "security", "5" => "syslog", "6" => "printer", "7" => "network", "8" => "UUCP", "9" => "clock", "10" => "security", "11" => "FTP", "12" => "NTP", "13" => "log_audit", "14" => "log_alert", "15" => "clock", "16" => "local0", "17" => "local1", "18" => "local2", "19" => "local3", "20" => "local4", "21" => "local5", "22" => "local6", "23" => "local7" ); # Längenvorgaben nach RFC3164 my %RFC3164len = ("TAG" => 32, # max. Länge TAG-Feld "DL" => 1024 # max. Lange Message insgesamt ); # Längenvorgaben nach RFC5425 my %RFC5425len = ("DL" => 8192, # max. Lange Message insgesamt mit TLS "HST" => 255, # max. Länge Hostname "ID" => 48, # max. Länge APP-NAME bzw. Ident "PID" => 128, # max. Länge Proc-ID "MID" => 32 # max. Länge MSGID ); ############################################################################### # Forward declarations # sub Log2Syslog_Log3slog($$$); use vars qw(%Log2Syslog_vHintsExt_en); use vars qw(%Log2Syslog_vHintsExt_de); ############################################################################### sub Log2Syslog_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "Log2Syslog_Define"; $hash->{UndefFn} = "Log2Syslog_Undef"; $hash->{DeleteFn} = "Log2Syslog_Delete"; $hash->{SetFn} = "Log2Syslog_Set"; $hash->{GetFn} = "Log2Syslog_Get"; $hash->{AttrFn} = "Log2Syslog_Attr"; $hash->{NotifyFn} = "Log2Syslog_eventlog"; $hash->{DbLog_splitFn} = "Log2Syslog_DbLogSplit"; $hash->{ReadFn} = "Log2Syslog_Read"; $hash->{AttrList} = "addStateEvent:1,0 ". "disable:1,0,maintenance ". "addTimestamp:0,1 ". "contDelimiter ". "exclErrCond:textField-long ". "logFormat:BSD,IETF ". "makeEvent:no,intern,reading ". "outputFields:sortable-strict,PRIVAL,FAC,SEV,TS,HOST,DATE,TIME,ID,PID,MID,SDFIELD,CONT ". "parseProfile:Automatic,BSD,IETF,TPLink-Switch,raw,ParseFn ". "parseFn:textField-long ". "respectSeverity:multiple-strict,Emergency,Alert,Critical,Error,Warning,Notice,Informational,Debug ". "octetCount:1,0 ". "protocol:UDP,TCP ". "port ". "rateCalcRerun ". "ssldebug:0,1,2,3 ". "sslCertPrefix ". "TLS:1,0 ". "timeout ". "useParsefilter:0,1 ". "useEOF:1,0 ". $readingFnAttributes ; eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) return; } ############################################################################### sub Log2Syslog_Define($@) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "Error: Perl module ".$MissModulSocket." is missing. Install it on Debian with: sudo apt-get install libio-socket-multicast-perl" if($MissModulSocket); return "Error: Perl module ".$MissModulNDom." is missing." if($MissModulNDom); # Example Sender: define splunklog Log2Syslog splunk.myds.me ident:Prod event:.* fhem:.* # Example Collector: define SyslogServer Log2Syslog delete($hash->{HELPER}{EVNTLOG}); delete($hash->{HELPER}{FHEMLOG}); delete($hash->{HELPER}{IDENT}); $hash->{MYHOST} = hostname(); # eigener Host (lt. RFC nur Hostname f. BSD) my $myfqdn = hostfqdn(); # MYFQDN eigener Host (f. IETF) $hash->{MYFQDN} = $myfqdn?$myfqdn:$hash->{MYHOST}; if(int(@a)-3 < 0){ # Einrichtung Servermode (Collector) $hash->{MODEL} = "Collector"; $hash->{PROFILE} = "Automatic"; readingsSingleUpdate ($hash, 'Parse_Err_No', 0, 1); # Fehlerzähler für Parse-Errors auf 0 Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - entering Syslog servermode ..."); Log2Syslog_initServer("$name,global"); } else { # Sendermode $hash->{MODEL} = "Sender"; Log2Syslog_setidrex($hash,$a[3]) if($a[3]); Log2Syslog_setidrex($hash,$a[4]) if($a[4]); Log2Syslog_setidrex($hash,$a[5]) if($a[5]); eval { "Hallo" =~ m/^$hash->{HELPER}{EVNTLOG}$/ } if($hash->{HELPER}{EVNTLOG}); return "Bad regexp: $@" if($@); eval { "Hallo" =~ m/^$hash->{HELPER}{FHEMLOG}$/ } if($hash->{HELPER}{FHEMLOG}); return "Bad regexp: $@" if($@); return "Bad regexp: starting with *" if((defined($hash->{HELPER}{EVNTLOG}) && $hash->{HELPER}{EVNTLOG} =~ m/^\*/) || (defined($hash->{HELPER}{FHEMLOG}) && $hash->{HELPER}{FHEMLOG} =~ m/^\*/)); # nur Events dieser Devices an NotifyFn weiterleiten, NOTIFYDEV wird gesetzt wenn möglich notifyRegexpChanged($hash, $hash->{HELPER}{EVNTLOG}) if($hash->{HELPER}{EVNTLOG}); $hash->{PEERHOST} = $a[2]; # Destination Host (Syslog Server) } $hash->{SEQNO} = 1; # PROCID in IETF, wird kontinuierlich hochgezählt $logInform{$hash->{NAME}} = "Log2Syslog_fhemlog"; # Funktion die in hash %loginform für $name eingetragen wird $hash->{HELPER}{SSLVER} = "n.a."; # Initialisierung $hash->{HELPER}{SSLALGO} = "n.a."; # Initialisierung $hash->{HELPER}{LTIME} = time(); # Init Timestmp f. Ratenbestimmung $hash->{HELPER}{OLDSEQNO} = $hash->{SEQNO}; # Init Sequenznummer f. Ratenbestimmung $hash->{HELPER}{OLDSTATE} = "initialized"; $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden # Versionsinformationen setzen Log2Syslog_setVersionInfo($hash); readingsBeginUpdate($hash); readingsBulkUpdate($hash, "SSL_Version", "n.a."); readingsBulkUpdate($hash, "SSL_Algorithm", "n.a."); readingsBulkUpdate($hash, "Transfered_logs_per_minute", 0); readingsBulkUpdate($hash, "state", "initialized") if($hash->{MODEL}=~/Sender/); readingsEndUpdate($hash,1); Log2Syslog_trate($hash); # regelm. Berechnung Transfer Rate starten return undef; } ################################################################################################# # Syslog Collector (Server-Mode) initialisieren # (im Collector Model) ################################################################################################# sub Log2Syslog_initServer($) { my ($a) = @_; my ($name,$global) = split(",",$a); my $hash = $defs{$name}; my $err; RemoveInternalTimer($hash, "Log2Syslog_initServer"); return if(IsDisabled($name) || $hash->{SERVERSOCKET}); if($init_done != 1 || Log2Syslog_IsMemLock($hash)) { InternalTimer(gettimeofday()+1, "Log2Syslog_initServer", "$name,$global", 0); return; } # Inititialisierung FHEM ist fertig -> Attribute geladen my $port = AttrVal($name, "TLS", 0)?AttrVal($name, "port", 6514):AttrVal($name, "port", 1514); my $protocol = lc(AttrVal($name, "protocol", "udp")); my $lh = ($global ? ($global eq "global" ? undef : $global) : ($hash->{IPV6} ? "::1" : "127.0.0.1")); Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - Opening socket on interface \"$global\" ..."); if($protocol =~ /udp/) { $hash->{SERVERSOCKET} = IO::Socket::INET->new( Domain => ($hash->{IPV6} ? AF_INET6() : AF_UNSPEC), # Linux bug LocalHost => $lh, Proto => $protocol, LocalPort => $port, ReuseAddr => 1 ); if(!$hash->{SERVERSOCKET}) { $err = "Can't open Syslog Collector at $port: $!"; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - $err"); readingsSingleUpdate ($hash, 'state', $err, 1); return; } $hash->{FD} = $hash->{SERVERSOCKET}->fileno(); $hash->{PORT} = $hash->{SERVERSOCKET}->sockport(); } else { $lh = "global" if(!$lh); my $ret = TcpServer_Open($hash,$port,$lh); if($ret) { $err = "Can't open Syslog TCP Collector at $port: $ret"; Log2Syslog_Log3slog ($hash, 1, "Log2Syslog $name - $err"); readingsSingleUpdate ($hash, 'state', $err, 1); return; } } $hash->{PROTOCOL} = $protocol; $hash->{SEQNO} = 1; # PROCID wird kontinuierlich pro empfangenen Datensatz hochgezählt $hash->{HELPER}{OLDSEQNO} = $hash->{SEQNO}; # Init Sequenznummer f. Ratenbestimmung $hash->{INTERFACE} = $lh?$lh:"global"; Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - port $hash->{PORT}/$protocol opened for Syslog Collector on interface \"$hash->{INTERFACE}\""); readingsSingleUpdate ($hash, "state", "initialized", 1); delete($readyfnlist{"$name.$port"}); $selectlist{"$name.$port"} = $hash; return; } ######################################################################################################## # Syslog Collector Daten empfangen (im Collector-Mode) # # !!!!! Achtung !!!!! # Kontextswitch des $hash beachten: initialer TCP-Server <-> temporärer TCP-Server ohne SERVERSOCKET # ######################################################################################################## # called from the global loop, when the select for hash->{FD} reports data sub Log2Syslog_Read($@) { my ($hash,$reread) = @_; my $socket = $hash->{SERVERSOCKET}; my ($err,$sev,$data,$ts,$phost,$pl,$ignore,$st,$len,$mlen,$evt,$pen); return if($init_done != 1); # maximale Länge des Syslog-Frames als Begrenzung falls kein EOF # vom Sender initiiert wird (Endlosschleife vermeiden) $mlen = 16384; $len = 8192; if($hash->{TEMPORARY}) { # temporäre Instanz angelegt durch TcpServer_Accept ($st,$data,$hash) = Log2Syslog_getifdata($hash,$len,$mlen,$reread); } my $name = $hash->{NAME}; return if(IsDisabled($name) || Log2Syslog_IsMemLock($hash)); my $pp = $hash->{PROFILE}; my $mevt = AttrVal($name, "makeEvent", "intern"); # wie soll Reading/Event erstellt werden my $sevevt = AttrVal($name, "respectSeverity", ""); # welcher Schweregrad soll berücksichtigt werden (default: alle) if($pp =~ /BSD/) { # BSD-Format $len = $RFC3164len{DL}; } elsif ($pp =~ /IETF/) { # IETF-Format $len = $RFC5425len{DL}; } if($socket) { ($st,$data,$hash) = Log2Syslog_getifdata($hash,$len,$mlen,$reread); } if($data) { # parse Payload my (@load,$mlen,$msg,$tail); if($data =~ /^(?(\d+))\s(?.*)/s) { # Syslog Sätze mit Octet Count -> Transmission of Syslog Messages over TCP https://tools.ietf.org/html/rfc6587 my $i = 0; $mlen = $+{mlen}; $tail = $+{tail}; $msg = substr($tail,0,$mlen); chomp $msg; push @load, $msg; $tail = substr($tail,$mlen); Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name -> LEN$i: $mlen, MSG$i: $msg, TAIL$i: $tail"); while($tail && $tail =~ /^(?(\d+))\s(?.*)/s) { $i++; $mlen = $+{mlen}; $tail = $+{tail}; $msg = substr($tail,0,$mlen); chomp $msg; push @load, $msg; $tail = substr($tail,$mlen); Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name -> LEN$i: $mlen, MSG$i: $msg, TAIL$i: $tail"); } } else { @load = split("[\r\n]",$data); } foreach my $line (@load) { ($err,$ignore,$sev,$phost,$ts,$pl) = Log2Syslog_parsePayload($hash,$line); $hash->{SEQNO}++; if($err) { $pen = ReadingsVal($name, "Parse_Err_No", 0); $pen++; readingsSingleUpdate($hash, 'Parse_Err_No', $pen, 1); $st = "parse error - see logfile"; } elsif ($ignore) { Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name -> dataset was ignored by parseFn"); } else { return if($sevevt && $sevevt !~ m/$sev/); # Message nicht berücksichtigen $st = "active"; if($mevt =~ /intern/) { # kein Reading, nur Event $pl = "$phost: $pl"; Log2Syslog_Trigger($hash,$ts,$pl); } elsif ($mevt =~ /reading/) { # Reading, Event abhängig von event-on-.* readingsSingleUpdate($hash, "MSG_$phost", $pl, 1); } else { # Reading ohne Event readingsSingleUpdate($hash, "MSG_$phost", $pl, 0); } } $evt = ($st eq $hash->{HELPER}{OLDSTATE})?0:1; readingsSingleUpdate($hash, "state", $st, $evt); $hash->{HELPER}{OLDSTATE} = $st; } } return; } ############################################################################### # Daten vom Interface holen # # Die einzige Aufgabe der Instanz mit SERVERSOCKET ist TcpServer_Accept # durchzufuehren (und evtl. noch Statistiken). Durch den Accept wird eine # weitere Instanz des gleichen Typs angelegt die eine Verbindung repraesentiert # und im ReadFn die eigentliche Arbeit macht: # # - ohne SERVERSOCKET dafuer mit CD/FD, PEER und PORT. CD/FD enthaelt den # neuen Filedeskriptor. # - mit TEMPORARY (damit es nicht gespeichert wird) # - SNAME verweist auf die "richtige" Instanz, damit man die Attribute # abfragen kann. # - TcpServer_Accept traegt den neuen Filedeskriptor in die globale %selectlist # ein. Damit wird ReadFn von fhem.pl/select mit dem temporaeren Instanzhash # aufgerufen, wenn Daten genau bei dieser Verbindung anstehen. # (sSiehe auch "list TYPE=FHEMWEB", bzw. "man -s2 accept") # ############################################################################### sub Log2Syslog_getifdata($$@) { my ($hash,$len,$mlen,$reread) = @_; my $name = $hash->{NAME}; my $socket = $hash->{SERVERSOCKET}; my $protocol = lc(AttrVal($name, "protocol", "udp")); my ($eof,$buforun) = (0,0); if($hash->{TEMPORARY}) { # temporäre Instanz abgelegt durch TcpServer_Accept $protocol = "tcp"; } my $st = ReadingsVal($name,"state","active"); my ($data,$ret); if(!$reread) { if($socket && $protocol =~ /udp/) { # UDP Datagramm empfangen Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ####################################################### "); Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ######### new Syslog UDP Receive ######### "); Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ####################################################### "); unless($socket->recv($data, $len)) { Log2Syslog_Log3slog ($hash, 3, "Log2Syslog $name - Seq \"$hash->{SEQNO}\" invalid data: $data"); $data = '' if(length($data) == 0); $st = "receive error - see logfile"; } else { my $dl = length($data); chomp $data; Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name - Buffer ".$dl." chars ready to parse:\n$data"); } return ($st,$data,$hash); } elsif ($protocol =~ /tcp/) { if($hash->{SERVERSOCKET}) { # Accept and create a child my $nhash = TcpServer_Accept($hash, "Log2Syslog"); return ($st,$data,$hash) if(!$nhash); $nhash->{CD}->blocking(0); if($nhash->{SSL}) { my $sslver = $nhash->{CD}->get_sslversion(); my $sslalgo = $nhash->{CD}->get_fingerprint(); readingsSingleUpdate($hash, "SSL_Version", $sslver, 1); readingsSingleUpdate($hash, "SSL_Algorithm", $sslalgo, 1); } return ($st,$data,$hash); } # Child, $hash ist Hash der temporären Instanz my $sname = $hash->{SNAME}; my $cname = $hash->{NAME}; my $shash = $defs{$sname}; # Hash des Log2Syslog-Devices bei temporärer TCP-Serverinstanz my $uef = AttrVal($sname, "useEOF", 0); my $tlsv = ReadingsVal($sname,"SSL_Version",''); Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ####################################################### "); Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ######### new Syslog TCP Receive ######### "); Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ####################################################### "); Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - await EOF: $uef, SSL: $tlsv"); Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - childname: $cname"); $st = ReadingsVal($sname,"state","active"); my $c = $hash->{CD}; if($c) { $shash->{HELPER}{TCPPADDR} = $hash->{PEER}; my $buf; my $off = 0; $ret = sysread($c, $buf, $len); # returns undef on error, 0 at end of file and Integer, number of bytes read on success. if(!defined($ret) && $! == EWOULDBLOCK){ # error $hash->{wantWrite} = 1 if(TcpServer_WantWrite($hash)); $hash = $shash; Log2Syslog_Log3slog ($hash, 2, "Log2Syslog $sname - ERROR - TCP stack error: $!"); return ($st,undef,$hash); } elsif (!$ret) { # EOF or error Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - Connection closed for $cname: ".(defined($ret) ? 'EOF' : $!)); if(!defined($ret)) { # error CommandDelete(undef, $cname); $hash = $shash; return ($st,undef,$hash); } else { # EOF $eof = 1; $data = $hash->{BUF}; CommandDelete(undef, $cname); } } if(!$eof) { $hash->{BUF} .= $buf; Log2Syslog_Log3slog ($shash, 5, "Log2Syslog $sname - Add $ret chars to buffer:\n$buf") if($uef && !$hash->{SSL}); } if($hash->{SSL} && $c->can('pending')) { while($c->pending()) { sysread($c, $buf, 1024); $hash->{BUF} .= $buf; } } $buforun = (length($hash->{BUF}) >= $mlen)?1:0; if(!$uef || $hash->{SSL} || $buforun) { $data = $hash->{BUF}; delete $hash->{BUF}; $hash = $shash; if($data) { my $dl = length($data); chomp $data; Log2Syslog_Log3slog ($shash, 2, "Log2Syslog $sname - WARNING - Buffer overrun ! Enforce parse data.") if($buforun); Log2Syslog_Log3slog ($shash, 5, "Log2Syslog $sname - Buffer $dl chars ready to parse:\n$data"); } return ($st,$data,$hash); } else { if($eof) { $hash = $shash; my $dl = length($data); chomp $data; Log2Syslog_Log3slog ($shash, 5, "Log2Syslog $sname - Buffer $dl chars after EOF ready to parse:\n$data") if($data); return ($st,$data,$hash); } } } } else { $st = "error - no socket opened"; $data = ''; return ($st,$data,$hash); } } return ($st,undef,$hash); } ############################################################################### # Parsen Payload für Syslog-Server # (im Collector Model) ############################################################################### sub Log2Syslog_parsePayload($$) { my ($hash,$data) = @_; my $name = $hash->{NAME}; my $pp = AttrVal($name, "parseProfile", $hash->{PROFILE}); my $pr = (AttrVal($name, "protocol", "UDP")); my $severity = ""; my $facility = ""; my @evf = split(",",AttrVal($name, "outputFields", "FAC,SEV,ID,CONT")); # auszugebene Felder im Event/Reading my $ignore = 0; my ($Mmm,$dd,$delimiter,$day,$ietf,$err,$pl,$tail); $data = Log2Syslog_parsefilter($data) if(AttrVal($name,"useParsefilter",0)); # Steuerzeichen werden entfernt (Achtung auch CR/LF) Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ######### Parse Message ######### "); Log2Syslog_Log3slog ($hash, 5, "Log2Syslog $name - parse profile: $pp"); # Hash zur Umwandlung Felder in deren Variablen my ($prival,$ts,$host,$date,$time,$id,$pid,$mid,$sdfield,$cont); my $fac = ""; my $sev = ""; my %fh = (PRIVAL => \$prival, FAC => \$fac, SEV => \$sev, TS => \$ts, HOST => \$host, DATE => \$date, TIME => \$time, ID => \$id, PID => \$pid, MID => \$mid, SDFIELD => \$sdfield, CONT => \$cont, DATA => \$data ); # Sender Host / IP-Adresse ermitteln, $phost wird Reading im Event my ($phost) = Log2Syslog_evalPeer($hash); Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - raw message -> $data"); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # Istzeit Ableitung $year = $year+1900; if($pp =~ /^Automatic/) { $pp = "raw"; Log2Syslog_Log3slog($name, 4, "Log2Syslog $name - Analyze message format automatically ..."); $data =~ /^<(?\d{1,3})>(?\w{3}).*$/; $tail = $+{tail}; # Test auf BSD-Format if($tail && " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec " =~ /\s$tail\s/) { $pp = "BSD"; } else { # Test auf IETF-Format $data =~ /^<(?\d{1,3})>(?\d{0,2})\s?(?\d{4}-\d{2}-\d{2})T(?