diff --git a/fhem/CHANGED b/fhem/CHANGED
index 9f459f5ee..0aed52271 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,12 +1,14 @@
# 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.
+ - feature: allowed module added. allowedCommands, basicAuth, password,
+ globalpassword attributes moved to this module.
- bugfix: YAMAHA_AVR: fixing not working volumeStraight set command
- change: FB_CALLLIST: start call processing only when "event:" is triggered
by corresponding FB_CALLMONITOR device.
- - change: FB_CALLMONITOR: default value for attr fritzbox-remote-phonebook-via
- has been changed to "tr064" (previous: "web") to ensure
- no problems with new Fritz!OS web layout.
- Besides TR-064 is official supported by AVM.
+ - change: FB_CALLMONITOR: default value for attr
+ fritzbox-remote-phonebook-via has been changed to "tr064"
+ (previous: "web") to ensure no problems with new Fritz!OS web
+ layout. Besides TR-064 is official supported by AVM.
- feature: PHILIPS_AUDIO: Favorite station selection
- feature: YAMAHA_AVR: new set commands and readings for tuner control
- new set commands tunerFrequency,tunerPreset for
diff --git a/fhem/FHEM/01_FHEMWEB.pm b/fhem/FHEM/01_FHEMWEB.pm
index 90d4669ce..7f8349633 100755
--- a/fhem/FHEM/01_FHEMWEB.pm
+++ b/fhem/FHEM/01_FHEMWEB.pm
@@ -218,10 +218,22 @@ FW_SecurityCheck($$)
!grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}));
my $motd = AttrVal("global", "motd", "");
if($motd =~ "^SecurityCheck") {
- my @list = grep { !AttrVal($_, "basicAuth", undef) }
- devspec2array("TYPE=FHEMWEB");
- $motd .= (join(",", sort @list)." has no basicAuth attribute.\n")
- if(@list);
+ my @list1 = devspec2array("TYPE=FHEMWEB");
+ my @list2 = devspec2array("TYPE=allowed");
+ my @list3;
+ for my $l (@list1) { # This is a hack, as hardcoded to basicAuth
+ next if(!$defs{$l});
+ my $fnd = 0;
+ for my $a (@list2) {
+ next if(!$defs{$a});
+ my $vf = AttrVal($a, "validFor","");
+ $fnd = 1 if((!$vf || $vf =~ m/\b$l\b/) && AttrVal($a, "basicAuth",""));
+ }
+ push @list3, $l if(!$fnd);
+ }
+ $motd .= (join(",", sort @list3).
+ " has no associated allowed device with basicAuth.\n")
+ if(@list3);
$attr{global}{motd} = $motd;
}
$modules{FHEMWEB}{NotifyFn}= "FW_Notify";
@@ -362,44 +374,24 @@ FW_Read($$)
#############################
- # BASIC HTTP AUTH
- my @headerOptions = grep /OPTIONS/, @FW_httpheader; # Need example
- my $basicAuth = AttrVal($FW_wname, "basicAuth", undef);
- if($basicAuth) {
- my $secret = $FW_httpheader{Authorization};
- $secret =~ s/^Basic //i if($secret);
- my $pwok = ($secret && $secret eq $basicAuth);
- if($secret && $basicAuth =~ m/^{.*}$/ || $headerOptions[0]) {
- eval "use MIME::Base64";
- if($@) {
- Log3 $FW_wname, 1, $@;
+ # AUTH
+ if(!defined($FW_chash->{Authenticated})) {
+ my $ret = Authenticate($FW_chash, \%FW_httpheader);
+ if($ret == 0) {
+ $FW_chash->{Authenticated} = 0; # not needed
- } else {
- my ($user, $password) = split(":", decode_base64($secret));
- $pwok = eval $basicAuth;
- Log3 $FW_wname, 1, "basicAuth expression: $@" if($@);
- }
+ } elsif($ret == 1) {
+ $FW_chash->{Authenticated} = 1; # ok
+
+ } else {
+ TcpServer_WriteBlocking($hash,
+ $FW_chash->{".httpAuthHeader"}.
+ $FW_headercors.
+ "Content-Length: 0\r\n\r\n");
+ delete $hash->{CONTENT_LENGTH};
+ FW_Read($hash, 1) if($hash->{BUF});
+ return;
}
- if($headerOptions[0]) {
- TcpServer_WriteBlocking($hash,
- "HTTP/1.1 200 OK\r\n".
- $FW_headercors.
- "Content-Length: 0\r\n\r\n");
- delete $hash->{CONTENT_LENGTH};
- FW_Read($hash, 1) if($hash->{BUF});
- return;
- };
- if(!$pwok) {
- my $msg = AttrVal($FW_wname, "basicAuthMsg", "Fhem: login required");
- TcpServer_WriteBlocking($hash,
- "HTTP/1.1 401 Authorization Required\r\n".
- "WWW-Authenticate: Basic realm=\"$msg\"\r\n".
- $FW_headercors.
- "Content-Length: 0\r\n\r\n");
- delete $hash->{CONTENT_LENGTH};
- FW_Read($hash, 1) if($hash->{BUF});
- return;
- };
}
#############################
@@ -1847,8 +1839,7 @@ FW_style($$)
my ($cmd, $msg) = @_;
my @a = split(" ", $cmd);
- my $ac = AttrVal($FW_wname,"allowedCommands","");
- return if($ac && $ac !~ m/\b$a[0]\b/);
+ return if(!Authorized($FW_chash, "cmd", $a[0]));
my $start = "
";
@@ -2173,11 +2164,9 @@ FW_fC($@)
my ($cmd, $unique) = @_;
my $ret;
if($unique) {
- $ret = AnalyzeCommand($FW_chash, $cmd,
- AttrVal($FW_wname,"allowedCommands",undef));
+ $ret = AnalyzeCommand($FW_chash, $cmd);
} else {
- $ret = AnalyzeCommandChain($FW_chash, $cmd,
- AttrVal($FW_wname,"allowedCommands",undef));
+ $ret = AnalyzeCommandChain($FW_chash, $cmd);
}
return $ret;
}
@@ -2185,52 +2174,65 @@ FW_fC($@)
sub
FW_Attr(@)
{
- my @a = @_;
- my $hash = $defs{$a[1]};
- my $name = $hash->{NAME};
+ my ($type, $devName, $attrName, @param) = @_;
+ my $hash = $defs{$devName};
my $sP = "stylesheetPrefix";
my $retMsg;
- if($a[0] eq "set" && $a[2] eq "HTTPS") {
+ if($type eq "set" && $attrName eq "HTTPS") {
TcpServer_SetSSL($hash);
}
- if($a[0] eq "set") { # Converting styles
- if($a[2] eq "smallscreen" || $a[2] eq "touchpad") {
- $attr{$name}{$sP} = $a[2];
- $retMsg="$name: attribute $a[2] deprecated, converted to $sP";
- $a[3] = $a[2]; $a[2] = $sP;
- }
- }
- if($a[2] eq $sP) {
- # AttrFn is called too early, we have to set/del the attr here
- if($a[0] eq "set") {
- $attr{$name}{$sP} = (defined($a[3]) ? $a[3] : "default");
- FW_readIcons($attr{$name}{$sP});
- } else {
- delete $attr{$name}{$sP};
+ if($type eq "set") { # Converting styles
+ if($attrName eq "smallscreen" || $attrName eq "touchpad") {
+ $attr{$devName}{$sP} = $attrName;
+ $retMsg="$devName: attribute $attrName deprecated, converted to $sP";
+ $param[0] = $attrName; $attrName = $sP;
}
}
- if($a[2] eq "iconPath" && $a[0] eq "set") {
- foreach my $pe (split(":", $a[3])) {
+ if($attrName eq $sP) {
+ # AttrFn is called too early, we have to set/del the attr here
+ if($type eq "set") {
+ $attr{$devName}{$sP} = (defined($param[0]) ? $param[0] : "default");
+ FW_readIcons($attr{$devName}{$sP});
+ } else {
+ delete $attr{$devName}{$sP};
+ }
+ }
+
+ if(($attrName eq "allowedCommands" ||
+ $attrName eq "basicAuth" ||
+ $attrName eq "basicAuthMsg")
+ && $type eq "set") {
+ my $aName = "allowed_$devName";
+ my $exists = ($defs{$aName} ? 1 : 0);
+ AnalyzeCommand(undef, "defmod $aName allowed");
+ AnalyzeCommand(undef, "attr $aName validFor $devName");
+ AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@param));
+ return "$devName: ".($exists ? "modifying":"creating").
+ " device $aName for attribute $attrName";
+ }
+
+ if($attrName eq "iconPath" && $type eq "set") {
+ foreach my $pe (split(":", $param[0])) {
$pe =~ s+\.\.++g;
FW_readIcons($pe);
}
}
- if($a[2] eq "JavaScripts" && $a[0] eq "set") { # create some attributes
+ if($attrName eq "JavaScripts" && $type eq "set") { # create some attributes
my (%a, @add);
map { $a{$_} = 1 } split(" ", $modules{FHEMWEB}{AttrList});
map {
$_ =~ s+.*/++; $_ =~ s/.js$//; $_ =~ s/fhem_//; $_ .= "Param";
push @add, $_ if(!$a{$_} && $_ !~ m/^-/);
- } split(" ", $a[3]);
+ } split(" ", $param[0]);
$modules{FHEMWEB}{AttrList} .= " ".join(" ",@add) if(@add);
}
- if($a[2] eq "csrfToken" && $a[0] eq "set") {
- my $csrf = $a[3];
+ if($attrName eq "csrfToken" && $type eq "set") {
+ my $csrf = $param[0];
if($csrf eq "random") {
my ($x,$y) = gettimeofday();
$csrf = rand($y)*rand($x);
@@ -2238,7 +2240,7 @@ FW_Attr(@)
$hash->{CSRFTOKEN} = $csrf;
}
- if($a[2] eq "csrfToken" && $a[0] eq "del") {
+ if($attrName eq "csrfToken" && $type eq "del") {
delete($hash->{CSRFTOKEN});
}
@@ -2937,50 +2939,13 @@ FW_widgetOverride($$)
- addStateEvent
-
- - allowedCommands
- A comma separated list of commands allowed from this FHEMWEB
- instance.
If set to an empty list , (i.e. comma only)
- then this FHEMWEB instance will be read-only.
If set to
- get,set
, then this FHEMWEB instance will only allow
- regular usage of the frontend by clicking the icons/buttons/sliders but
- not changing any configuration.
-
-
- This attribute intended to be used together with hiddenroom/hiddengroup
-
-
- Note:allowedCommands should work as intended, but no guarantee
- can be given that there is no way to circumvent it. If a command is
- allowed it can be issued by URL manipulation also for devices that are
- hidden.
-
- allowfrom
-
- - basicAuth, basicAuthMsg
- request a username/password authentication for access. You have to set
- the basicAuth attribute to the Base64 encoded value of
- <user>:<password>, e.g.:
- # Calculate first the encoded string with the commandline program
- $ echo -n fhemuser:secret | base64
- ZmhlbXVzZXI6c2VjcmV0
- fhem.cfg:
- attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
-
- You can of course use other means of base64 encoding, e.g. online
- Base64 encoders. If basicAuthMsg is set, it will be displayed in the
- popup window when requesting the username/password.
-
- If the argument of basicAuth is enclosed in {}, then it will be
- evaluated, and the $user and $password variable will be set to the
- values entered. If the return value is true, then the password will be
- accepted.
- Example:
-
- attr WEB basicAuth { "$user:$password" eq "admin:secret" }
-
+ - allowedCommands, basicAuth, basicAuthMsg
+ Please create these attributes for the corresponding allowed device, they are deprecated for the FHEMWEB
+ instance from now on.
@@ -3647,52 +3612,13 @@ FW_widgetOverride($$)
- addStateEvent
-
- - allowedCommands
- Eine Komma getrennte Liste der erlaubten Befehle. Bei einer leeren
- Liste (, dh. nur ein Komma) wird dieser FHEMWEB-Instanz "read-only".
-
Falls es auf get,set
gesetzt ist, dann sind in dieser
- FHEMWEB Instanz keine Konfigurationsänderungen möglich, nur
- "normale" Bedienung der Schalter/etc.
-
- Dieses Attribut sollte zusammen mit dem hiddenroom/hiddengroup
- Attributen verwendet werden.
-
- Achtung: allowedCommands sollte wie hier beschrieben
- funktionieren, allerdings können wir keine Garantie geben,
- daß man sie nicht überlisten, und Schaden anrichten kann.
-
-
- allowfrom
-
- - basicAuth, basicAuthMsg
- Fragt username/password zur Autentifizierung ab. Es gibt mehrere
- Varianten:
-
- - falls das Argument nicht in {} eingeschlossen ist, dann wird
- es als base64 kodiertes benutzername:passwort interpretiert.
- Um sowas zu erzeugen kann man entweder einen der zahlreichen
- Webdienste verwenden, oder das base64 Programm. Beispiel:
-
- $ echo -n fhemuser:secret | base64
- ZmhlbXVzZXI6c2VjcmV0
- fhem.cfg:
- attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
-
-
- - Werden die Argumente in {} angegeben, wird es als perl-Ausdruck
- ausgewertet, die Variablen $user and $password werden auf die
- eingegebenen Werte gesetzt. Falls der Rückgabewert wahr ist,
- wird die Anmeldung akzeptiert.
-
- Beispiel:
-
- attr WEB basicAuth { "$user:$password" eq "admin:secret" }
-
-
-
+ - allowedCommands, basicAuth, basicAuthMsg
+ Diese Attribute müssen ab sofort bei dem passenden allowed Gerät angelegt werden, und sind
+ für eine FHEMWEB Instanz unerwünscht.
diff --git a/fhem/FHEM/96_allowed.pm b/fhem/FHEM/96_allowed.pm
new file mode 100755
index 000000000..684cc0e09
--- /dev/null
+++ b/fhem/FHEM/96_allowed.pm
@@ -0,0 +1,446 @@
+##############################################
+# $Id$
+package main;
+
+use strict;
+use warnings;
+
+#####################################
+sub
+allowed_Initialize($)
+{
+ my ($hash) = @_;
+
+ $hash->{DefFn} = "allowed_Define";
+ $hash->{AuthorizeFn} = "allowed_Authorize";
+ $hash->{AuthenticateFn} = "allowed_Authenticate";
+ $hash->{AttrFn} = "allowed_Attr";
+ $hash->{AttrList} = "disable:0,1 validFor allowedCommands allowedDevices ".
+ "basicAuth basicAuthMsg password globalpassword";
+ $hash->{UndefFn} = "allowed_Undef";
+}
+
+
+#####################################
+sub
+allowed_Define($$)
+{
+ my ($hash, $def) = @_;
+ my @l = split(" ", $def);
+
+ if(@l > 2) {
+ my %list;
+ for(my $i=2; $i<@l; $i++) {
+ $list{$l[$i]} = 1;
+ }
+ $hash->{devices} = \%list;
+ }
+ $auth_refresh = 1;
+ readingsSingleUpdate($hash, "state", "active", 0);
+ return undef;
+}
+
+sub
+allowed_Undef($$)
+{
+ $auth_refresh = 1;
+ return undef;
+}
+
+#####################################
+# Return 0 for don't care, 1 for Allowed, 2 for forbidden.
+sub
+allowed_Authorize($$$$)
+{
+ my ($me, $cl, $type, $arg) = @_;
+
+ return 0 if($me->{disabled});
+ return 0 if($me->{validFor} && $me->{validFor} !~ m/\b$cl->{SNAME}\b/);
+
+ if($type eq "cmd") {
+ return 0 if(!$me->{allowedCommands});
+ return ($me->{allowedCommands} =~ m/\b$arg\b/) ? 1 : 2;
+ }
+
+ if($type eq "devicename") {
+ return 0 if(!$me->{allowedDevices});
+ return ($me->{allowedDevices} =~ m/\b$arg\b/) ? 1 : 2;
+ }
+
+ return 0;
+}
+
+#####################################
+# Return 0 for authentication not needed, 1 for auth-ok, 2 for wrong password
+sub
+allowed_Authenticate($$$$)
+{
+ my ($me, $cl, $param) = @_;
+
+ return 0 if($me->{disabled});
+ return 0 if($me->{validFor} && $me->{validFor} !~ m/\b$cl->{SNAME}\b/);
+ my $aName = $me->{NAME};
+
+ if($cl->{TYPE} eq "FHEMWEB") {
+ my $basicAuth = AttrVal($aName, "basicAuth", undef);
+ return 0 if(!$basicAuth);
+
+ my $FW_httpheader = $param;
+ my $secret = $FW_httpheader->{Authorization};
+ $secret =~ s/^Basic //i if($secret);
+ my $pwok = ($secret && $secret eq $basicAuth); # Base64
+ if($secret && $basicAuth =~ m/^{.*}$/) {
+ eval "use MIME::Base64";
+ if($@) {
+ Log3 $aName, 1, $@;
+
+ } else {
+ my ($user, $password) = split(":", decode_base64($secret));
+ $pwok = eval $basicAuth;
+ Log3 $aName, 1, "basicAuth expression: $@" if($@);
+ }
+ }
+
+ return 1 if($pwok);
+
+ my $msg = AttrVal($aName, "basicAuthMsg", "FHEM: login required");
+ $cl->{".httpAuthHeader"} = "HTTP/1.1 401 Authorization Required\r\n".
+ "WWW-Authenticate: Basic realm=\"$msg\"\r\n";
+ return 2;
+ }
+
+ if($cl->{TYPE} eq "telnet") {
+ my $pw = AttrVal($aName, "password", undef);
+ if(!$pw) {
+ $pw = AttrVal($aName, "globalpassword", undef);
+ $pw = undef if($pw && $cl->{NAME} !~ m/^telnet:127.0.0.1/);
+ }
+ return 0 if(!$pw);
+ return 2 if(!defined($param));
+ if($pw =~ m/^{.*}$/) {
+ my $password = $param;
+ my $ret = eval $pw;
+ Log3 $aName, 1, "password expression: $@" if($@);
+ return ($ret ? 1 : 2);
+ }
+ return ($pw eq $param) ? 1 : 2;
+ }
+
+ return 0;
+}
+
+
+sub
+allowed_Attr(@)
+{
+ my ($type, $devName, $attrName, @param) = @_;
+ my $hash = $defs{$devName};
+
+ my $set = ($type eq "del" ? 0 : (!defined($param[0]) || $param[0]) ? 1 : 0);
+
+ if($attrName eq "disable") {
+ readingsSingleUpdate($hash, "state", $set ? "disabled" : "active", 1);
+ if($set) {
+ $hash->{disable} = 1;
+ } else {
+ delete($hash->{disable});
+ }
+
+ } elsif($attrName eq "allowedCommands" || # hoping for some speedup
+ $attrName eq "allowedDevices" ||
+ $attrName eq "validFor") {
+ if($set) {
+ $hash->{$attrName} = join(" ", @param);
+ } else {
+ delete($hash->{$attrName});
+ }
+
+ } elsif(($attrName eq "basicAuth" ||
+ $attrName eq "password" || $attrName eq "globalpassword") &&
+ $type eq "set") {
+ foreach my $d (devspec2array("TYPE=(FHEMWEB|telnet)")) {
+ delete $defs{$d}{Authenticated} if($defs{$d});
+ }
+ }
+
+ return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+
+allowed
+
+
+
+
+ Define
+
+ define <name> allowed <deviceList>
+
+ Authorize execution of commands and modification of devices based on the
+ frontend used.
+ Note: this module should work as intended, but no guarantee
+ can be given that there is no way to circumvent it.
+ Examples:
+
+ define allowedWEB allowed
+ attr allowedWEB validFor WEB,WEBphone,WEBtablet
+ attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
+ attr allowedWEB allowedCommands set,get
+
+ define allowedTelnet allowed
+ attr allowedTelnet validFor telnetPort
+ attr allowedTelnet password secret
+
+
+
+
+
+ Set:
+
+
+ Get
+
+
+ Attributes
+
+ - disable
+
+
+ - allowedCommands
+ A comma separated list of commands allowed from the matching frontend
+ (see validFor).
+ If set to an empty list , (i.e. comma only)
+ then no comands are allowed. If set to get,set
, then only
+ a "regular" usage is allowed via set and get, but changing any
+ configuration is forbidden.
+
+
+
+ - allowedDevices
+ A comma separated list of device names which can be manipulated via the
+ matching frontend (see validFor).
+
+
+
+ - basicAuth, basicAuthMsg
+ request a username/password authentication for FHEMWEB access. You have
+ to set the basicAuth attribute to the Base64 encoded value of
+ <user>:<password>, e.g.:
+ # Calculate first the encoded string with the commandline program
+ $ echo -n fhemuser:secret | base64
+ ZmhlbXVzZXI6c2VjcmV0
+ # Set the FHEM attribute
+ attr allowed_WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
+
+ You can of course use other means of base64 encoding, e.g. online
+ Base64 encoders.
+
+ If the argument of basicAuth is enclosed in { }, then it will be
+ evaluated, and the $user and $password variable will be set to the
+ values entered. If the return value is true, then the password will be
+ accepted.
+
+ If basicAuthMsg is set, it will be displayed in the
+ popup window when requesting the username/password.
+
+ Example:
+
+ attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
+
+
+
+
+ - password
+ Specify a password for telnet instances, which has to be entered as the
+ very first string after the connection is established. If the argument
+ is enclosed in {}, then it will be evaluated, and the $password
+ variable will be set to the password entered. If the return value is
+ true, then the password will be accepted. If this parameter is
+ specified, FHEM sends telnet IAC requests to supress echo while
+ entering the password. Also all returned lines are terminated with
+ \r\n.
+ Example:
+
+
+ attr allowed_tPort password secret
+ attr allowed_tPort password {"$password" eq "secret"}
+
+
+ Note: if this attribute is set, you have to specify a password as the
+ first argument when using fhem.pl in client mode:
+
+ perl fhem.pl localhost:7072 secret "set lamp on"
+
+
+
+
+ - globalpassword
+ Just like the attribute password, but a password will only required for
+ non-local connections.
+
+
+
+
+ - validFor
+ A comma separated list of frontend names. Currently supported frontends
+ are all devices connected through the FHEM TCP/IP library, e.g. telnet
+ and FHEMWEB. If set, the rules specified via the other attributes will
+ only apply to the frontends in the list. If not set, the rules apply to
+ all frontends.
+
+
+
+
+
+
+
+=end html
+
+=begin html_DE
+
+
+allowed
+
+
+
+
+ Define
+
+ define <name> allowed <deviceList>
+
+ Authorisiert das Ausführen von Kommandos oder das Ändern von
+ Geräten abhängig vom verwendeten Frontend.
+
+ Achtung: das Modul sollte wie hier beschrieben funktionieren,
+ allerdings können wir keine Garantie geben, daß man sie nicht
+ überlisten, und Schaden anrichten kann.
+
+ Beispiele:
+
+ define allowedWEB allowed
+ attr allowedWEB validFor WEB,WEBphone,WEBtablet
+ attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }
+ attr allowedWEB allowedCommands set,get
+
+ define allowedTelnet allowed
+ attr allowedTelnet validFor telnetPort
+ attr allowedTelnet password secret
+
+
+
+
+
+ Set:
+
+
+ Get
+
+
+ Attribute
+
+ - disable
+
+
+
+ - allowedCommands
+ Eine Komma getrennte Liste der erlaubten Befehle des passenden
+ Frontends (siehe validFor). Bei einer leeren Liste (, dh. nur ein
+ Komma) wird dieser Frontend "read-only".
+ Falls es auf get,set
gesetzt ist, dann sind in dieser
+ Frontend keine Konfigurationsänderungen möglich, nur
+ "normale" Bedienung der Schalter/etc.
+
+
+
+ - allowedDevices
+ Komma getrennte Liste von Gerätenamen, die mit dem passenden
+ Frontend (siehe validFor) geändert werden können.
+
+
+
+ - basicAuth, basicAuthMsg
+ Betrifft nur FHEMWEB Instanzen (siehe validFor): Fragt username /
+ password zur Autentifizierung ab. Es gibt mehrere Varianten:
+
+ - falls das Argument nicht in { } eingeschlossen ist, dann wird
+ es als base64 kodiertes benutzername:passwort interpretiert.
+ Um sowas zu erzeugen kann man entweder einen der zahlreichen
+ Webdienste verwenden, oder das base64 Programm. Beispiel:
+
+ $ echo -n fhemuser:secret | base64
+ ZmhlbXVzZXI6c2VjcmV0
+ fhem.cfg:
+ attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
+
+
+ - Werden die Argumente in { } angegeben, wird es als perl-Ausdruck
+ ausgewertet, die Variablen $user and $password werden auf die
+ eingegebenen Werte gesetzt. Falls der Rückgabewert wahr ist,
+ wird die Anmeldung akzeptiert.
+
+ Beispiel:
+
+ attr allwedWEB basicAuth { "$user:$password" eq "admin:secret" }
+
+
+
+
+
+
+
+ - password
+ Betrifft nur telnet Instanzen (siehe validFor): Bezeichnet ein
+ Passwort, welches als allererster String eingegeben werden muss,
+ nachdem die Verbindung aufgebaut wurde. Wenn das Argument in { }
+ eingebettet ist, dann wird es als Perl-Ausdruck ausgewertet, und die
+ Variable $password mit dem eingegebenen Passwort verglichen. Ist der
+ zurückgegebene Wert wahr (true), wird das Passwort akzeptiert.
+ Falls dieser Parameter gesetzt wird, sendet FHEM telnet IAC Requests,
+ um ein Echo während der Passworteingabe zu unterdrücken.
+ Ebenso werden alle zurückgegebenen Zeilen mit \r\n abgeschlossen.
+
+ Beispiel:
+
+
+ attr allowed_tPort password secret
+ attr allowed_tPort password {"$password" eq "secret"}
+
+
+ Hinweis: Falls dieses Attribut gesetzt wird, muss als erstes Argument
+ ein Passwort angegeben werden, wenn fhem.pl im Client-mode betrieben
+ wird:
+
+
+ perl fhem.pl localhost:7072 secret "set lamp on"
+
+
+
+
+
+ - globalpassword
+ Betrifft nur telnet Instanzen (siehe validFor): Entspricht dem
+ Attribut password; ein Passwort wird aber ausschließlich für
+ nicht-lokale Verbindungen verlangt.
+
+
+
+ - validFor
+ Komma separierte Liste von Forntend-Instanznamen. Aktuell werden nur
+ Frontends unterstützt, die das FHEM TCP/IP Bibliothek verwenden.
+ Falls gesetzt, dann gelten die mit den anderen Attributen
+ spezifizierten Regeln nur für diese Instanzen. Falls nicht
+ gesetzt, dann sind alle Frontends betroffen.
+
+
+
+
+
+
+=end html_DE
+
+=cut
diff --git a/fhem/FHEM/98_telnet.pm b/fhem/FHEM/98_telnet.pm
index 44d6123f9..223211484 100644
--- a/fhem/FHEM/98_telnet.pm
+++ b/fhem/FHEM/98_telnet.pm
@@ -31,6 +31,7 @@ telnet_Initialize($)
Hlp=>"[utf8|latin1],query and set the character encoding for the current telnet session" );
$cmds{encoding} = \%lhash;
}
+
sub
CommandTelnetEncoding($$)
{
@@ -60,16 +61,29 @@ telnet_SecurityCheck($$)
!grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}));
my $motd = AttrVal("global", "motd", "");
if($motd =~ "^SecurityCheck") {
- my @list = grep { !(AttrVal($_, "password", undef) ||
- AttrVal($_, "globalpassword", undef)) }
- devspec2array("TYPE=telnet");
- $motd .= (join(",", sort @list).
- " has no password/globalpassword attribute.\n")
- if(@list);
+ my @list1 = devspec2array("TYPE=telnet");
+ my @list2 = devspec2array("TYPE=allowed");
+ my @list3;
+ for my $l (@list1) { # This is a hack, as hardcoded to basicAuth
+ next if(!$defs{$l});
+ my $fnd = 0;
+ for my $a (@list2) {
+ next if(!$defs{$a});
+ my $vf = AttrVal($a, "validFor","");
+ $fnd = 1 if((!$vf || $vf =~ m/\b$l\b/) &&
+ (AttrVal($a, "password","") ||
+ AttrVal($a, "globalpassword","")));
+ }
+ push @list3, $l if(!$fnd);
+ }
+ $motd .= (join(",", sort @list3).
+ " has no associated allowed device with password/globalpassword.\n")
+ if(@list3);
$attr{global}{motd} = $motd;
}
delete $modules{telnet}{NotifyFn};
return;
+ return;
}
##########################
@@ -165,19 +179,6 @@ telnet_Define($$$)
}
}
-sub
-telnet_pw($$)
-{
- my ($sname, $cname) = @_;
- my $pw = $attr{$sname}{password};
- return $pw if($pw);
-
- $pw = $attr{$sname}{globalpassword};
- return $pw if($pw && $cname !~ m/^telnet:127.0.0.1/);
-
- return undef;
-}
-
##########################
sub
telnet_Read($)
@@ -193,8 +194,10 @@ telnet_Read($)
syswrite($chash->{CD}, sprintf("%c%c%c", 255, 253, 0) )
if( AttrVal($name, "encoding", "") ); #DO BINARY
$chash->{CD}->flush();
+ my $auth = Authenticate($chash, undef);
syswrite($chash->{CD}, sprintf("%c%c%cPassword: ", 255, 251, 1)) # WILL ECHO
- if(telnet_pw($name, $chash->{NAME}));
+ if($auth);
+ $chash->{Authenticated} = 0 if(!$auth);
return;
}
@@ -217,8 +220,7 @@ telnet_Read($)
$buf =~ s/\r//g;
my $sname = ($hash->{isClient} ? $name : $hash->{SNAME});
- my $pw = telnet_pw($sname, $name);
- if($pw) {
+ if(!defined($hash->{Authenticated}) || $hash->{Authenticated}) {
$buf =~ s/\xff..//g; # Telnet IAC stuff
$buf =~ s/\xfd(.)//; # Telnet Do ?
syswrite($hash->{CD}, sprintf("%c%c%c", 0xff, 0xfc, ord($1)))
@@ -232,31 +234,23 @@ telnet_Read($)
my ($cmd, $rest) = split("\n", $hash->{BUF}, 2);
$hash->{BUF} = $rest;
- if(!$hash->{pwEntered}) {
- if($pw) {
- syswrite($hash->{CD}, sprintf("%c%c%c\r\n", 255, 252, 1)); # WONT ECHO
+ if(!defined($hash->{Authenticated})) {
+ syswrite($hash->{CD}, sprintf("%c%c%c\r\n", 255, 252, 1)); # WONT ECHO
- $ret = ($pw eq $cmd);
- if($pw =~ m/^{.*}$/) { # Expression as pw
- my $password = $cmd;
- $ret = eval $pw;
- Log3 $name, 1, "password expression: $@" if($@);
- }
-
- if($ret) {
- $hash->{pwEntered} = 1;
- next;
+ if(Authenticate($hash, $cmd) == 1) {
+ $hash->{Authenticated} = 1;
+ next;
+ } else {
+ if($hash->{isClient}) {
+ telnet_ClientDisconnect($hash, 0);
} else {
- if($hash->{isClient}) {
- telnet_ClientDisconnect($hash, 0);
- } else {
- delete($hash->{rcvdQuit});
- CommandDelete(undef, $name);
- }
- return;
+ delete($hash->{rcvdQuit});
+ CommandDelete(undef, $name);
}
+ return;
}
}
+
$gotCmd = 1;
if($cmd) {
if($cmd =~ m/\\ *$/) { # Multi-line
@@ -268,8 +262,7 @@ telnet_Read($)
undef($hash->{prevlines});
}
$cmd = latin1ToUtf8($cmd) if( $hash->{encoding} eq "latin1" );
- $ret = AnalyzeCommandChain($hash, $cmd,
- AttrVal($sname,"allowedCommands",undef));
+ $ret = AnalyzeCommandChain($hash, $cmd);
push @ret, $ret if(defined($ret));
}
} else {
@@ -288,7 +281,7 @@ telnet_Read($)
$ret .= ($hash->{prevlines} ? "> " : $hash->{prompt}." ")
if($gotCmd && $hash->{showPrompt} && !$hash->{rcvdQuit});
- $ret =~ s/\n/\r\n/g if($pw); # only for DOS telnet
+ $ret =~ s/\n/\r\n/g if($hash->{Authenticated}); # only for DOS telnet
telnet_Output($hash,$ret);
if($hash->{rcvdQuit}) {
@@ -323,16 +316,30 @@ telnet_Output($$)
sub
telnet_Attr(@)
{
+ my ($type, $devName, $attrName, @param) = @_;
my @a = @_;
- my $hash = $defs{$a[1]};
+ my $hash = $defs{$devName};
- if($a[0] eq "set" && $a[2] eq "SSL") {
+ if($type eq "set" && $attrName eq "SSL") {
TcpServer_SetSSL($hash);
if($hash->{CD}) {
my $ret = IO::Socket::SSL->start_SSL($hash->{CD});
- Log3 $a[1], 1, "$hash->{NAME} start_SSL: $ret" if($ret);
+ Log3 $devName, 1, "$hash->{NAME} start_SSL: $ret" if($ret);
}
}
+
+ if(($attrName eq "allowedCommands" ||
+ $attrName eq "password" ||
+ $attrName eq "globalpassword" ) && $type eq "set") {
+ my $aName = "allowed_$devName";
+ my $exists = ($defs{$aName} ? 1 : 0);
+ AnalyzeCommand(undef, "defmod $aName allowed");
+ AnalyzeCommand(undef, "attr $aName validFor $devName");
+ AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@param));
+ return "$devName: ".($exists ? "modifying":"creating").
+ " device $aName for attribute $attrName";
+ }
+
return undef;
}
@@ -387,8 +394,10 @@ telnet_ActivateInform($;$)
Examples:
define tPort telnet 7072 global
- attr tPort globalpassword mySecret
attr tPort SSL
+ attr allowed_tPort allowed
+ attr allowed_tPort validFor tPort
+ attr allowed_tPort globalpassword mySecret
Note: The old global attribute port is automatically converted to a
telnet instance with the name telnetPort. The global allowfrom attibute is
@@ -424,37 +433,6 @@ telnet_ActivateInform($;$)
Attributes:
- allowedCommands
-
-
- - password
- Specify a password, which has to be entered as the very first string
- after the connection is established. If the argument is enclosed in {},
- then it will be evaluated, and the $password variable will be set to
- the password entered. If the return value is true, then the password
- will be accepted. If thies parameter is specified, fhem sends telnet
- IAC requests to supress echo while entering the password.
- Also all returned lines are terminated with \r\n.
- Example:
-
-
- attr tPort password secret
- attr tPort password {"$password" eq "secret"}
-
-
- Note: if this attribute is set, you have to specify a password as the
- first argument when using fhem.pl in client mode:
-
- perl fhem.pl localhost:7072 secret "set lamp on"
-
-
-
-
- - globalpassword
- Just like the attribute password, but a password will only required for
- non-local connections.
-
-
- prompt
Sets the string for the telnet prompt, the default is fhem>
@@ -538,8 +516,10 @@ telnet_ActivateInform($;$)
Beispiele:
define tPort telnet 7072 global
- attr tPort globalpassword mySecret
attr tPort SSL
+ attr allowed_tPort allowed
+ attr allowed_tPort validFor tPort
+ attr allowed_tPort globalpassword mySecret
Hinweis: Das alte (pre 5.3) "global attribute port" wird automatisch in
eine telnet-Instanz mit dem Namen telnetPort umgewandelt. Im Rahmen dieser
@@ -579,43 +559,6 @@ telnet_ActivateInform($;$)
Attribute
- allowedCommands
-
-
- - password
- Bezeichnet ein Passwort, welches als allererster String eingegeben
- werden muss, nachdem die Verbindung aufgebaut wurde. Wenn das Argument
- in {} eingebettet ist, dann wird es als Perl-Ausdruck ausgewertet, und
- die Variable $password mit dem eingegebenen Passwort verglichen. Ist
- der zurückgegebene Wert wahr (true), wurde das Passwort
- akzeptiert. Falls dieser Parameter gesetzt wird, sendet fhem
- telnet IAC Requests, um ein Echo während der Passworteingabe zu
- unterdrücken. Ebenso werden alle zurückgegebenen Zeilen mit
- \r\n abgeschlossen.
-
- Beispiel:
-
-
- attr tPort password secret
- attr tPort password {"$password" eq "secret"}
-
-
- Hinweis: Falls dieses Attribut gesetzt wird, muss als erstes Argument
- ein Passwort angegeben werden, wenn fhem.pl im Client-mode betrieben
- wird:
-
-
- perl fhem.pl localhost:7072 secret "set lamp on"
-
-
-
-
-
- - globalpassword
- Entspricht dem Attribut password; ein Passwort wird aber
- ausschließlich für nicht-lokale Verbindungen verlangt.
-
-
- prompt
Gibt die Zeichenkette an, welche in der Telnet-Sitzung als
diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html
index 758a99a31..b67080c0b 100644
--- a/fhem/docs/commandref_frame.html
+++ b/fhem/docs/commandref_frame.html
@@ -93,6 +93,7 @@
Helper modules
at
+ at
autocreate
average
Calendar
diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html
index fd2748e2b..16b153943 100644
--- a/fhem/docs/commandref_frame_DE.html
+++ b/fhem/docs/commandref_frame_DE.html
@@ -93,6 +93,7 @@
Hilfs (Erweiterungs-) Module
at
+ at
autocreate
average
Calendar
diff --git a/fhem/fhem.pl b/fhem/fhem.pl
index 01c0e2776..f83a3ff7a 100755
--- a/fhem/fhem.pl
+++ b/fhem/fhem.pl
@@ -81,6 +81,7 @@ sub PrintHash($$);
sub ReadingsNum($$$);
sub ReadingsTimestamp($$$);
sub ReadingsVal($$$);
+sub RefreshAuthList();
sub RemoveInternalTimer($);
sub ReplaceEventMap($$$);
sub ResolveDateWildcards($@);
@@ -224,6 +225,9 @@ use vars qw($lastDefChange); # number of last def/attr change
use vars qw(@structChangeHist); # Contains the last 10 structural changes
use vars qw($cmdFromAnalyze); # used by the warnings-sub
use vars qw($featurelevel);
+use vars qw(@authorize); # List of authorization devices
+use vars qw(@authenticate); # List of authentication devices
+use vars qw($auth_refresh);
my $AttrList = "verbose:0,1,2,3,4,5 room group comment:textField-long alias ".
"eventMap userReadings:textField-long";
@@ -231,7 +235,7 @@ my $currcfgfile=""; # current config/include file
my $currlogfile; # logfile, without wildcards
my $cvsid = '$Id$';
my $duplidx=0; # helper for the above pool
-my $evalSpecials; # Used by EvalSpecials->AnalyzeCommand parameter passing
+my $evalSpecials; # Used by EvalSpecials->AnalyzeCommand
my $intAtCnt=0;
my $logopened = 0; # logfile opened or using stdout
my $namedef = "where is a single device name, a list separated by komma (,) or a regexp. See the devspec section in the commandref.html for details.\n";
@@ -910,7 +914,7 @@ CommandIOWrite($$)
sub
AnalyzeCommandChain($$;$)
{
- my ($c, $cmd, $allowed) = @_;
+ my ($c, $cmd) = @_;
my @ret;
if($cmd =~ m/^[ \t]*(#.*)?$/) { # Save comments
@@ -933,7 +937,7 @@ AnalyzeCommandChain($$;$)
my $subcmd;
while(defined($subcmd = shift @cmdList)) {
$subcmd =~ s/SeMiCoLoN/;/g;
- my $lret = AnalyzeCommand($c, $subcmd, $allowed);
+ my $lret = AnalyzeCommand($c, $subcmd);
push(@ret, $lret) if(defined($lret));
}
@cmdList = @saveCmdList;
@@ -946,15 +950,15 @@ AnalyzeCommandChain($$;$)
sub
AnalyzePerlCommand($$;$)
{
- my ($cl, $cmd, $calledFromChain) = @_;
+ my ($cl, $cmd, $calledFromChain) = @_; # third parmeter is deprecated
+
+ return "Forbidden command $cmd." if($cl && !Authorized($cl, "cmd", "perl"));
- return "Forbidden command $cmd."
- if($cl && $cl->{".allowed"} && $cl->{".allowed"} !~ m/\bperl\b/);
$cmd =~ s/\\ *\n/ /g; # Multi-line. Probably not needed anymore
# Make life easier for oneliners:
- %value = ();
if($featurelevel <= 5.6) {
+ %value = ();
foreach my $d (keys %defs) {
$value{$d} = $defs{$d}{STATE}
}
@@ -994,9 +998,8 @@ AnalyzePerlCommand($$;$)
sub
AnalyzeCommand($$;$)
{
- my ($cl, $cmd, $allowed) = @_;
+ my ($cl, $cmd) = @_; # third parmeter is deprecated
- $cl->{".allowed"} = $allowed if($cl); # Forum #38276
$cmd = "" if(!defined($cmd)); # Forum #29963
$cmd =~ s/^(\n|[ \t])*//;# Strip space or \n at the begginning
$cmd =~ s/[ \t]*$//;
@@ -1009,7 +1012,7 @@ AnalyzeCommand($$;$)
}
if($cmd =~ m/^"(.*)"$/s) { # Shell code in bg, to be able to call us from it
- return "Forbidden command $cmd." if($allowed && $allowed !~ m/\bshell\b/);
+ return "Forbidden command $cmd." if($cl || !Authorized($cl,"cmd","shell"));
if($evalSpecials) {
map { $ENV{substr($_,1)} = $evalSpecials->{$_}; } keys %{$evalSpecials};
}
@@ -1041,7 +1044,7 @@ AnalyzeCommand($$;$)
$fn = $cmds{$fn}{ReplacedBy}
if(defined($cmds{$fn}) && defined($cmds{$fn}{ReplacedBy}));
- return "Forbidden command $fn." if($allowed && $allowed !~ m/\b$fn\b/);
+ return "Forbidden command $fn." if($cl && !Authorized($cl,"cmd",$fn));
#############
# autoload commands.
@@ -1077,6 +1080,11 @@ devspec2array($;$)
return "" if(!defined($name));
if(defined($defs{$name})) {
+ if($cl && !Authorized($cl, "devicename", $name)) {
+ Log 4, "Forbidden device $name";
+ return "";
+ }
+
# FHEM2FHEM LOG mode fake device, avoid local set/attr/etc operations on it
return "FHEM2FHEM_FAKE_$name" if($defs{$name}{FAKEDEVICE});
return $name;
@@ -1156,6 +1164,7 @@ devspec2array($;$)
push @ret,@res;
}
return $name if(!@ret && !$isAttr);
+ @ret = grep { Authorized($cl, "devicename", $_) } @ret if($cl);
return @ret;
}
@@ -4526,4 +4535,54 @@ Each($$;$) # can be used e.g. in at, Forum #40022
return $arr[$idx];
}
+##################
+# Return 1 if Authorized, else 0
+sub
+Authorized($$$)
+{
+ my ($cl, $type, $arg) = @_;
+
+ return 1 if(!$init_done || !$cl || !$cl->{SNAME}); # Safeguarding
+ RefreshAuthList() if($auth_refresh);
+
+ foreach my $a (@authorize) {
+ my $r = CallFn($a, "AuthorizeFn", $defs{$a}, $cl, $type, $arg);
+ return 1 if($r == 1);
+ return 0 if($r == 2);
+ }
+ return 1;
+}
+
+##################
+# Return 0 if not needed, 1 if authenticated, 2 if authentication failed
+sub
+Authenticate($$)
+{
+ my ($cl, $arg) = @_;
+
+ return 1 if(!$init_done || !$cl || !$cl->{SNAME}); # Safeguarding
+ RefreshAuthList() if($auth_refresh);
+
+ foreach my $a (@authenticate) {
+ my $r = CallFn($a, "AuthenticateFn", $defs{$a}, $cl, $arg);
+ return $r if($r);
+ }
+ return 0;
+}
+
+sub
+RefreshAuthList()
+{
+ @authorize = ();
+ @authenticate = ();
+
+ foreach my $d (sort keys %defs) {
+ my $h = $defs{$d};
+ next if(!$h->{TYPE} || !$modules{$h->{TYPE}});
+ push @authorize, $d if($modules{$h->{TYPE}}{AuthorizeFn});
+ push @authenticate, $d if($modules{$h->{TYPE}}{AuthenticateFn});
+ }
+ $auth_refresh = 0;
+}
+
1;