From 5c70f260527239cdaa4faae60c9602ec4d6df959 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Sun, 17 Mar 2019 20:38:19 +0000 Subject: [PATCH] 93_Log2Syslog: contrib 5.4.0 git-svn-id: https://svn.fhem.de/fhem/trunk@18949 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/DS_Starter/93_Log2Syslog.pm | 3409 ++++++++++++++++++++++ 1 file changed, 3409 insertions(+) create mode 100644 fhem/contrib/DS_Starter/93_Log2Syslog.pm diff --git a/fhem/contrib/DS_Starter/93_Log2Syslog.pm b/fhem/contrib/DS_Starter/93_Log2Syslog.pm new file mode 100644 index 000000000..960ce9a8f --- /dev/null +++ b/fhem/contrib/DS_Starter/93_Log2Syslog.pm @@ -0,0 +1,3409 @@ +########################################################################################################################## +# $Id: 93_Log2Syslog.pm 18536 2019-02-08 21:08:47Z 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"; + +# Versions History intern: +our %Log2Syslog_vNotesIntern = ( + "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.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
  • 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->{ReadFn} = "Log2Syslog_Read"; + + $hash->{AttrList} = "addStateEvent:1,0 ". + "disable:1,0,maintenance ". + "addTimestamp:0,1 ". + "contDelimiter ". + "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 ". + "ssldebug:0,1,2,3 ". + "sslCertPrefix ". + "TLS:1,0 ". + "timeout ". + "protocol:UDP,TCP ". + "port ". + "rateCalcRerun ". + $readingFnAttributes + ; +return undef; +} + +############################################################################### +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 + $hash->{VERSION} = (reverse sort(keys %Log2Syslog_vNotesIntern))[0]; + + $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"; + + 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,$evt,$pen); + + return if($init_done != 1); + + my $mlen = 8192; # maximale Länge des Syslog-Frames als Begrenzung falls kein EOF + # vom Sender initiiert wird (Endlosschleife vermeiden) + if($hash->{TEMPORARY}) { + # temporäre Instanz angelegt durch TcpServer_Accept + $len = 8192; + ($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}; + + } else { + # raw oder User eigenes Format + $len = 8192; + } + + 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")); + if($hash->{TEMPORARY}) { + # temporäre Instanz abgelegt durch TcpServer_Accept + $protocol = "tcp"; + } + my $st = ReadingsVal($name,"state","active"); + my ($data,$ret); + + if($socket && $protocol =~ /udp/) { + # UDP Datagramm empfangen + Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ####################################################### "); + Log2Syslog_Log3slog ($hash, 4, "Log2Syslog $name - ######### new Syslog UDP Parsing ######### "); + 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"; + } + } 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); + } + + my $sname = $hash->{SNAME}; + my $cname = $hash->{NAME}; + my $shash = $defs{$sname}; # Hash des Log2Syslog-Devices bei temporärer TCP-Serverinstanz + + Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ####################################################### "); + Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ######### new Syslog TCP Parsing ######### "); + Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - ####################################################### "); + Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - childname: $cname"); + $st = ReadingsVal($sname,"state","active"); + my $c = $hash->{CD}; + if($c) { + $shash->{HELPER}{TCPPADDR} = $hash->{PEER}; + if(!$reread) { + 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; + return ($st,undef,$hash); + + } elsif (!$ret) { + # end of file + CommandDelete(undef, $cname); + $hash = $shash; + Log2Syslog_Log3slog ($shash, 4, "Log2Syslog $sname - Connection closed for $cname: ".(defined($ret) ? 'EOF' : $!)); + return ($st,undef,$hash); + + } + $hash->{BUF} .= $buf; + + if($hash->{SSL} && $c->can('pending')) { + while($c->pending()) { + sysread($c, $buf, 1024); + $hash->{BUF} .= $buf; + } + } + + $data = $hash->{BUF}; + delete $hash->{BUF}; + $hash = $shash; + Log2Syslog_Log3slog ($shash, 5, "Log2Syslog $sname - Buffer content:\n$data"); + } + } + + } else { + $st = "error - no socket opened"; + $data = ''; + } + +return ($st,$data,$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 $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); + + # 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 = "unknown"; + 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(?