2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 09:16:53 +00:00

34_NUT: fixed possible buffer overflow; rewrote reception of data

git-svn-id: https://svn.fhem.de/fhem/trunk@6612 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
narsskrarc 2014-09-24 08:23:05 +00:00
parent 91744d09be
commit b05e7cba0e
2 changed files with 165 additions and 89 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- bugfix: 34_NUT: fixed possible buffer overflow, rewrote reception of data
- bugfix: SYSMON: idletime on multicore, warnings - bugfix: SYSMON: idletime on multicore, warnings
- change: 09_CUL_FHTTK.pm: modified set option for sync, open and closed - change: 09_CUL_FHTTK.pm: modified set option for sync, open and closed
- feature: SYSMON: HTML/Text output for SYSMON-CloneDummies - feature: SYSMON: HTML/Text output for SYSMON-CloneDummies

View File

@ -27,15 +27,15 @@
# Aliase nehmen (z.B. voltage) bzw. modifizierte Bezeichnungen (z.B. inputVoltage)? # Aliase nehmen (z.B. voltage) bzw. modifizierte Bezeichnungen (z.B. inputVoltage)?
# B - attr pollInterval: Wertebereich prüfen (min. 5, max. ?) # B - attr pollInterval: Wertebereich prüfen (min. 5, max. ?)
# B - readingFnAttributes implementieren # B - readingFnAttributes implementieren
# C - Zusätzliche berechnete Werte - vielleicht auch per attr?
# C - per GET könnte man alle Werte der USV verfügbar machen # C - per GET könnte man alle Werte der USV verfügbar machen
# D - SET implementieren, um diverse Dinge mit der USV anstellen zu können (Test, Ausschalten, ...) # D - SET implementieren, um diverse Dinge mit der USV anstellen zu können (Test, Ausschalten, ...)
# D - Für die Web-Oberfläche wäre es vermutlich schick, wenn man die veränderbaren Variablen auch verändern könnte,
# inklusive den ENUMs und RANGEs, die NUT für die Werte anbietet
# #
# #
# FIXME # FIXME
# - DevIo_Expect funktioniert nicht, da ich den Status auf OL setze. Da die Daten sowieso nicht schnell genug da sind # - Fehlermeldung in fhem.log: "Notify Loop for..." Wieso?
# und ich NUT_Read brauche, wäre es wohl besser, die Anfrage einfach zu schreiben.
# Wie kriege ich raus, ob die Verbindung noch steht?
# - Fehlermeldung in fhem.log: "Notify Loop for ..." Wieso?
# #
@ -54,6 +54,9 @@ sub NUT_Read($);
sub NUT_ListVar($); sub NUT_ListVar($);
sub NUT_Auswertung($); sub NUT_Auswertung($);
sub NUT_DbLog_split($); sub NUT_DbLog_split($);
sub NUT_createVariables($);
sub NUT_makeReadings($);
sub NUT_addUnit($$);
# Definitionen für die Berechnung der Einheit aus dem Namen # Definitionen für die Berechnung der Einheit aus dem Namen
@ -129,7 +132,7 @@ sub NUT_Define($$) {
# Defaults setzen # Defaults setzen
$attr{$name}{pollState} = 5; $attr{$name}{pollState} = 10;
$attr{$name}{pollVal} = 60; $attr{$name}{pollVal} = 60;
$attr{$name}{disable} = 0; $attr{$name}{disable} = 0;
$attr{$name}{asReadings} = 'battery.charge battery.runtime input.voltage ups.load ups.power ups.realpower'; $attr{$name}{asReadings} = 'battery.charge battery.runtime input.voltage ups.load ups.power ups.realpower';
@ -185,6 +188,8 @@ sub NUT_ListVar($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{pollValState} += $attr{$name}{pollState};
if ($attr{$name}{disable} == 0) { if ($attr{$name}{disable} == 0) {
# TODO # TODO
# - Mechanismus, der verhindert, dass lauter Befehle abgesendet werden, während er noch auf Antworten wartet # - Mechanismus, der verhindert, dass lauter Befehle abgesendet werden, während er noch auf Antworten wartet
@ -197,15 +202,22 @@ sub NUT_ListVar($) {
} }
my $ups = $hash->{UpsName}; my $ups = $hash->{UpsName};
if ($hash->{pollValState} > $attr{$name}{pollVal}) {
$hash->{pollValState} = 0;
# Kompletten Datensatz anfordern
Log3 $name, 5, "Sending 'LIST VAR $ups'..."; Log3 $name, 5, "Sending 'LIST VAR $ups'...";
DevIo_SimpleWrite($hash, "LIST VAR $ups\n", 0); DevIo_SimpleWrite($hash, "LIST VAR $ups\n", 0);
} else {
# Nur Status anfordern
Log3 $name, 5, "Sending 'GET VAR $ups ups.status'...";
DevIo_SimpleWrite($hash, "GET VAR $ups ups.status\n", 0);
}
$hash->{WaitForAnswer} = 1; $hash->{WaitForAnswer} = 1;
} else { } else {
Log3 $name, 5, "NUT polling disabled."; Log3 $name, 5, "NUT polling disabled.";
} }
$hash->{pollValState} += $attr{$name}{pollState};
RemoveInternalTimer("pollTimer:".$name); RemoveInternalTimer("pollTimer:".$name);
InternalTimer(gettimeofday() + $attr{$name}{pollState}, "NUT_PollTimer", "pollTimer:".$name, 0); InternalTimer(gettimeofday() + $attr{$name}{pollState}, "NUT_PollTimer", "pollTimer:".$name, 0);
} }
@ -220,47 +232,128 @@ sub NUT_PollTimer($) {
} }
sub NUT_Auswertung($) { sub NUT_Auswertung($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $buf = $hash->{buffer}; my $buf = $hash->{buffer};
my @lines = split /\n/, $buf;
if (substr($buf, -1) ne "\n") {
# Letzte Zeile ist noch unvollständig
$hash->{buffer} = $lines[$#lines];
$lines[$#lines] = '';
if (length($hash->{buffer}) > 1024) {
# Notbremse, wenn eine Zeile zu lange wird
Log3 $name, 1, "NUT: Zeile > 1024 Zeichen!";
$hash->{buffer} = '';
}
} else {
$hash->{buffer} = '';
}
my %var = ();
foreach my $line (@lines) {
if (length($line) > 0) {
Log3 $name, 5, "NUT RX: $line";
my $arg = '';
if ($line =~ m/(.*) "(.*)"/) {
# Argument in "
$line = $1;
$arg = $2;
}
my @words = split / /, $line;
if ($words[0] eq 'VAR') {
# Variable erkannt
if ($words[1] eq $hash->{UpsName}) {
my $var = $words[2];
$hash->{helper}{$var} = NUT_addUnit($var, $arg);
# Sonderfälle
if ($var eq 'ups.status') {
# Status wird sofort übernommen
# Der Status wird ja oft abgefragt, um nichts zu verpassen. Damit das nicht jedes Mal
# Notifies gibt, wird dieser nur getriggert, wenn sich am Status etwas ändert.
# FIXME und was ist mit event-on-*-reading?
readingsSingleUpdate($hash, 'state', $hash->{helper}{'ups.status'}, $hash->{helper}{'ups.status'} ne $hash->{lastStatus});
$hash->{lastStatus} = $hash->{helper}{'ups.status'};
} elsif ($var eq 'ups.model' and not defined $attr{$name}{model}) {
$attr{$name}{model} = $hash->{helper}{$var};
} elsif ($var eq 'ups.serial' and not defined $attr{$name}{serNo}) {
$attr{$name}{serNo} = $hash->{helper}{$var};
}
} else {
Log3 $name, 1, "NUT $hash->{UpsName}: VAR from wrong UPS $words[1]";
}
} elsif ($words[0] eq 'BEGIN') {
# Anfang einer Liste - n/u
} elsif ($words[0] eq 'END') {
# Ende einer Liste
if ($words[2] eq 'VAR') {
# Ende einer Variablen-Liste
# Erzeugen von berechneten Variablen
NUT_createVariables($hash);
# Umwidmen der Variablen in Readings
NUT_makeReadings($hash);
}
} elsif ($words[0] eq 'ERR') {
# Fehlermeldungen
my $err = $words[1];
Log3 $name, 2, "NUT Error: $err";
readingsSingleUpdate($hash, 'lastError', $err, 1);
readingsSingleUpdate($hash, 'state', $err, 1);
if ($err=~ m/(ACCESS-DENIED|UNKNOWN-UPS)/) {
# Das sind Fehlermeldungen, die keine Hoffnung machen, dass es noch funktionieren könnte
$attr{$name}{disable} = 1;
}
} else {
# TODO Es gibt noch viele Antworten, die interessant sein könnten...
# http://www.networkupstools.org/docs/developer-guide.chunked/ar01s09.html
Log3 $name, 5, "NUT: not implemented: $line";
}
# Fehlermeldungen auswerten
while ($buf =~ m/(.*)^ERR (.+?)$(.*)/m) {
Log3 $name, 2, "NUT Error: $2";
readingsSingleUpdate($hash, 'lastError', $2, 1);
# Fehlermeldung rauswerfen
$buf = $1 . "\n" . $3;
delete $hash->{WaitForAnswer}; delete $hash->{WaitForAnswer};
} # if len > 0
} # foreach
}
sub NUT_addUnit($$) {
my ($var, $arg) = @_;
# Einheiten an die Werte anfügen
# Das soll ja eigentlich nicht passieren, aber da die interfaces nur sehr unvollständig passen, geht das nicht anders.
# Geht das effizienter?
my $excl = join('|', @nutunitsexclude);
unless ($var =~ m/($excl)/) {
foreach my $unit (@nutunits) {
if ($var =~ m/$unit->[0]/) {
$arg .= ' ' . $unit->[1];
last;
} }
$hash->{buffer} = $buf;
# Auswerten
if ($buf =~ m'(.*?)\s*^BEGIN LIST VAR (.+?)$(.*)^END LIST VAR \g2$\s*(.*)'ms) {
Log3 $name, 4, "Daten vor LIST VAR:\n'$1'" unless $1 eq "";
Log3 $name, 4, "Daten nach LIST VAR:\n'$4'" unless $4 eq "";
my $ups = $hash->{UpsName};
Log3 $name, 3, "Falsche USV: $2 statt $ups" unless $2 eq $ups;
# Relevante Daten in den Readings speichern
my @readings = split('\n', $3);
Log3 $name, 5, "Anzahl Readings von $name: $#readings";
delete $hash->{helper};
foreach (@readings) {
# Log3 $name, 5, "Test $_...";
if (m/VAR \w+ ([\w\.]+) "(.*)"/) {
$hash->{helper}{$1} = $2;
# Log3 $name, 5, "... found $1 -> $2";
} }
} }
# set Arrtibutes return $arg;
$attr{$name}{model} = $hash->{helper}{'ups.model'} unless defined $attr{$name}{model}; }
$attr{$name}{serNo} = $hash->{helper}{'ups.serial'} unless defined $attr{$name}{serNo};
sub NUT_createVariables($) {
my ($hash) = @_;
my $name = $hash->{NAME};
# Zusätzliche Werte berechnen, die die USV nicht zur Verfügung stellt
# Create derived readings
unless (defined $hash->{helper}{'ups.power'}) { unless (defined $hash->{helper}{'ups.power'}) {
if (defined $hash->{helper}{'ups.load'} and defined $hash->{helper}{'ups.power.nominal'}) { if (defined $hash->{helper}{'ups.load'} and defined $hash->{helper}{'ups.power.nominal'}) {
$hash->{helper}{'ups.power'} = $hash->{helper}{'ups.power.nominal'} * $hash->{helper}{'ups.load'} / 100; $hash->{helper}{'ups.power'} = $hash->{helper}{'ups.power.nominal'} * $hash->{helper}{'ups.load'} / 100;
@ -272,46 +365,28 @@ sub NUT_Auswertung($) {
} }
} }
# Einheiten an die Werte anfügen }
# Das soll ja eigentlich nicht passieren, aber da die interfaces nur sehr unvollständig passen, geht das nicht anders.
# Geht das effizienter?
my $excl = join('|', @nutunitsexclude);
foreach (keys %{$hash->{helper}}) {
unless (m/($excl)/) {
foreach my $unit (@nutunits) {
if (m/$unit->[0]/) {
$hash->{helper}{$_} .= ' ' . $unit->[1];
last;
}
}
}
}
# Log3 $name, 5, "Set Readings";
# Der Status wird ja oft abgefragt, um nichts zu verpassen. Damit das nicht jedes Mal
# Notifies gibt, wird dieser nur getriggert, wenn sich am Status etwas ändert. sub NUT_makeReadings($) {
my ($hash) = @_;
my $name = $hash->{NAME};
# Die USV-Variablen en bloc in Readings überführen
# FIXME und was ist mit event-on-*-reading? # FIXME und was ist mit event-on-*-reading?
readingsSingleUpdate($hash, 'state', $hash->{helper}{'ups.status'}, $hash->{helper}{'ups.status'} ne $hash->{lastStatus}); # FIXME Woher kommt die Fehlermeldung 'Notify loop for...' im Log?
$hash->{lastStatus} = $hash->{helper}{'ups.status'};
if ($hash->{pollValState} > $attr{$name}{pollVal}) {
$hash->{pollValState} = 0;
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
foreach (split (' ', $attr{$name}{asReadings})) { foreach (split (' ', $attr{$name}{asReadings})) {
readingsBulkUpdate($hash, $_, $hash->{helper}{$_}) if defined $hash->{helper}{$_}; readingsBulkUpdate($hash, $_, $hash->{helper}{$_}) if defined $hash->{helper}{$_};
} }
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
}
# FIXME Mehrere Readings im Buffer werden hier ignoriert
$hash->{buffer} = '';
delete $hash->{WaitForAnswer};
}
} }
sub NUT_DbLog_split($) { sub NUT_DbLog_split($) {
my ($event) = @_; my ($event) = @_;
my ($reading, $value) = split(": ", $event); my ($reading, $value) = split(": ", $event);
@ -373,7 +448,7 @@ sub NUT_DbLog_split($) {
<ul> <ul>
<li><a href="#disable">disable</a></li><br> <li><a href="#disable">disable</a></li><br>
<li><a name="">pollState</a><br> <li><a name="">pollState</a><br>
Polling interval in seconds for the state of the ups. Default: 5</li><br> Polling interval in seconds for the state of the ups. Default: 10</li><br>
<li><a name="">pollVal</a><br> <li><a name="">pollVal</a><br>
Polling interval in seconds of the other Readings. This should be a multiple of pollState. Default: 60</li><br> Polling interval in seconds of the other Readings. This should be a multiple of pollState. Default: 60</li><br>
<li><a name="">asReadings</a><br> <li><a name="">asReadings</a><br>
@ -433,7 +508,7 @@ sub NUT_DbLog_split($) {
<ul> <ul>
<li><a href="#disable">disable</a></li><br> <li><a href="#disable">disable</a></li><br>
<li><a name="">pollState</a><br> <li><a name="">pollState</a><br>
Polling-Intervall in Sekunden für den Status der USV. Default: 5</li><br> Polling-Intervall in Sekunden für den Status der USV. Default: 10</li><br>
<li><a name="">pollVal</a><br> <li><a name="">pollVal</a><br>
Polling-Intervall für die anderen Werte. Dieser Wert wird auf ein Vielfaches von pollState gerundet. Default: 60</li><br> Polling-Intervall für die anderen Werte. Dieser Wert wird auf ein Vielfaches von pollState gerundet. Default: 60</li><br>
<li><a name="NUT_asReadings">asReadings</a><br> <li><a name="NUT_asReadings">asReadings</a><br>