########################################################################################################################## # $Id: 93_Log2Syslog.pm 18981 2019-03-20 21:39:53Z 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.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.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->{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 ". "ssldebug:0,1,2,3 ". "sslCertPrefix ". "TLS:1,0 ". "timeout ". "protocol:UDP,TCP ". "port ". "rateCalcRerun ". $readingFnAttributes ; 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"; # 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,$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(?