2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-26 16:19:32 +00:00

96_allowed.pm: Authorization/Authentication modularized

git-svn-id: https://svn.fhem.de/fhem/trunk@10298 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2015-12-29 19:08:19 +00:00
parent a799cabab4
commit 6d26b4c934
7 changed files with 666 additions and 288 deletions

View File

@ -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

View File

@ -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 = "<div id=\"content\"><table><tr><td>";
my $end = "</td></tr></table></div>";
@ -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($$)
<ul>
<li><a href="#addStateEvent">addStateEvent</a></li>
<a name="allowedCommands"></a>
<li>allowedCommands<br>
A comma separated list of commands allowed from this FHEMWEB
instance.<br> If set to an empty list <code>, (i.e. comma only)</code>
then this FHEMWEB instance will be read-only.<br> If set to
<code>get,set</code>, then this FHEMWEB instance will only allow
regular usage of the frontend by clicking the icons/buttons/sliders but
not changing any configuration.<br>
This attribute intended to be used together with hiddenroom/hiddengroup
<br>
<b>Note:</b>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.</li><br>
<li><a href="#allowfrom">allowfrom</a></li>
</li><br>
<a name="basicAuth"></a>
<li>basicAuth, basicAuthMsg<br>
request a username/password authentication for access. You have to set
the basicAuth attribute to the Base64 encoded value of
&lt;user&gt;:&lt;password&gt;, e.g.:<ul>
# Calculate first the encoded string with the commandline program<br>
$ echo -n fhemuser:secret | base64<br>
ZmhlbXVzZXI6c2VjcmV0<br>
fhem.cfg:<br>
attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
</ul>
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.<br>
<br>
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:<br>
<code>
attr WEB basicAuth { "$user:$password" eq "admin:secret" }<br>
</code>
<li>allowedCommands, basicAuth, basicAuthMsg<br>
Please create these attributes for the corresponding <a
href="#allowed">allowed</a> device, they are deprecated for the FHEMWEB
instance from now on.
</li><br>
<a name="closeConn"></a>
@ -3647,52 +3612,13 @@ FW_widgetOverride($$)
<ul>
<li><a href="#addStateEvent">addStateEvent</a></li>
<a name="allowedCommands"></a>
<li>allowedCommands<br>
Eine Komma getrennte Liste der erlaubten Befehle. Bei einer leeren
Liste (, dh. nur ein Komma) wird dieser FHEMWEB-Instanz "read-only".
<br> Falls es auf <code>get,set</code> gesetzt ist, dann sind in dieser
FHEMWEB Instanz keine Konfigurations&auml;nderungen m&ouml;glich, nur
"normale" Bedienung der Schalter/etc.<br>
Dieses Attribut sollte zusammen mit dem hiddenroom/hiddengroup
Attributen verwendet werden. <br>
<b>Achtung:</b> allowedCommands sollte wie hier beschrieben
funktionieren, allerdings k&ouml;nnen wir keine Garantie geben,
da&szlig; man sie nicht &uuml;berlisten, und Schaden anrichten kann.
</li><br>
<li><a href="#allowfrom">allowfrom</a>
</li><br>
<a name="basicAuth"></a>
<li>basicAuth, basicAuthMsg<br>
Fragt username/password zur Autentifizierung ab. Es gibt mehrere
Varianten:
<ul>
<li>falls das Argument <b>nicht</b> 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:
<ul><code>
$ echo -n fhemuser:secret | base64<br>
ZmhlbXVzZXI6c2VjcmV0<br>
fhem.cfg:<br>
attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
</code></ul>
</li>
<li>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&uuml;ckgabewert wahr ist,
wird die Anmeldung akzeptiert.
Beispiel:<br>
<code>
attr WEB basicAuth { "$user:$password" eq "admin:secret" }<br>
</code>
</li>
</ul>
<li>allowedCommands, basicAuth, basicAuthMsg<br>
Diese Attribute m&uuml;ssen ab sofort bei dem passenden <a
href="#allowed">allowed</a> Ger&auml;t angelegt werden, und sind
f&uuml;r eine FHEMWEB Instanz unerw&uuml;nscht.
</li><br>
<a name="closeConn"></a>

446
fhem/FHEM/96_allowed.pm Executable file
View File

@ -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
<a name="allowed"></a>
<h3>allowed</h3>
<ul>
<br>
<a name="alloweddefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; allowed &lt;deviceList&gt;</code>
<br><br>
Authorize execution of commands and modification of devices based on the
frontend used.<br>
<b>Note:</b> this module should work as intended, but no guarantee
can be given that there is no way to circumvent it.<br><br>
Examples:
<ul><code>
define allowedWEB allowed<br>
attr allowedWEB validFor WEB,WEBphone,WEBtablet<br>
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
attr allowedWEB allowedCommands set,get<br><br>
define allowedTelnet allowed<br>
attr allowedTelnet validFor telnetPort<br>
attr allowedTelnet password secret<br>
</code></ul>
<br>
</ul>
<a name="allowedset"></a>
<b>Set:</b> <ul>N/A</ul><br>
<a name="allowedget"></a>
<b>Get</b> <ul>N/A</ul><br>
<a name="allowedattr"></a>
<b>Attributes</b>
<ul>
<li><a href="#disable">disable</a></li><br>
<a name="allowedCommands"></a>
<li>allowedCommands<br>
A comma separated list of commands allowed from the matching frontend
(see validFor).<br>
If set to an empty list <code>, (i.e. comma only)</code>
then no comands are allowed. If set to <code>get,set</code>, then only
a "regular" usage is allowed via set and get, but changing any
configuration is forbidden.<br>
</li><br>
<a name="allowedDevices"></a>
<li>allowedDevices<br>
A comma separated list of device names which can be manipulated via the
matching frontend (see validFor).
</li><br>
<a name="basicAuth"></a>
<li>basicAuth, basicAuthMsg<br>
request a username/password authentication for FHEMWEB access. You have
to set the basicAuth attribute to the Base64 encoded value of
&lt;user&gt;:&lt;password&gt;, e.g.:<ul>
# Calculate first the encoded string with the commandline program<br>
$ echo -n fhemuser:secret | base64<br>
ZmhlbXVzZXI6c2VjcmV0<br>
# Set the FHEM attribute<br>
attr allowed_WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
</ul>
You can of course use other means of base64 encoding, e.g. online
Base64 encoders.<br>
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.<br>
If basicAuthMsg is set, it will be displayed in the
popup window when requesting the username/password.<br>
Example:<br>
<ul><code>
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
</code></ul>
</li><br>
<a name="password"></a>
<li>password<br>
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:<br>
<ul>
<code>
attr allowed_tPort password secret<br>
attr allowed_tPort password {"$password" eq "secret"}
</code>
</ul>
Note: if this attribute is set, you have to specify a password as the
first argument when using fhem.pl in client mode:
<ul>
perl fhem.pl localhost:7072 secret "set lamp on"
</ul>
</li><br>
<a name="globalpassword"></a>
<li>globalpassword<br>
Just like the attribute password, but a password will only required for
non-local connections.
</li><br>
<a name="validFor"></a>
<li>validFor<br>
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.
</li>
</ul>
<br>
</ul>
=end html
=begin html_DE
<a name="allowed"></a>
<h3>allowed</h3>
<ul>
<br>
<a name="alloweddefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; allowed &lt;deviceList&gt;</code>
<br><br>
Authorisiert das Ausf&uuml;hren von Kommandos oder das &Auml;ndern von
Ger&auml;ten abh&auml;ngig vom verwendeten Frontend.<br>
<b>Achtung:</b> das Modul sollte wie hier beschrieben funktionieren,
allerdings k&ouml;nnen wir keine Garantie geben, da&szlig; man sie nicht
&uuml;berlisten, und Schaden anrichten kann.
<br>
Beispiele:
<ul><code>
define allowedWEB allowed<br>
attr allowedWEB validFor WEB,WEBphone,WEBtablet<br>
attr allowedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
attr allowedWEB allowedCommands set,get<br><br>
define allowedTelnet allowed<br>
attr allowedTelnet validFor telnetPort<br>
attr allowedTelnet password secret<br>
</code></ul>
<br>
</ul>
<a name="allowedset"></a>
<b>Set:</b> <ul>N/A</ul><br>
<a name="allowedget"></a>
<b>Get</b> <ul>N/A</ul><br>
<a name="allowedattr"></a>
<b>Attribute</b>
<ul>
<li><a href="#disable">disable</a>
</li><br>
<a name="allowedCommands"></a>
<li>allowedCommands<br>
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 <code>get,set</code> gesetzt ist, dann sind in dieser
Frontend keine Konfigurations&auml;nderungen m&ouml;glich, nur
"normale" Bedienung der Schalter/etc.
</li><br>
<a name="allowedDevices"></a>
<li>allowedDevices<br>
Komma getrennte Liste von Ger&auml;tenamen, die mit dem passenden
Frontend (siehe validFor) ge&auml;ndert werden k&ouml;nnen.
</li><br>
<a name="basicAuth"></a>
<li>basicAuth, basicAuthMsg<br>
Betrifft nur FHEMWEB Instanzen (siehe validFor): Fragt username /
password zur Autentifizierung ab. Es gibt mehrere Varianten:
<ul>
<li>falls das Argument <b>nicht</b> 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:
<ul><code>
$ echo -n fhemuser:secret | base64<br>
ZmhlbXVzZXI6c2VjcmV0<br>
fhem.cfg:<br>
attr WEB basicAuth ZmhlbXVzZXI6c2VjcmV0
</code></ul>
</li>
<li>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&uuml;ckgabewert wahr ist,
wird die Anmeldung akzeptiert.
Beispiel:<br>
<ul><code>
attr allwedWEB basicAuth { "$user:$password" eq "admin:secret" }<br>
</code></ul>
</li>
</ul>
</li><br>
<a name="password"></a>
<li>password<br>
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&uuml;ckgegebene Wert wahr (true), wird das Passwort akzeptiert.
Falls dieser Parameter gesetzt wird, sendet FHEM telnet IAC Requests,
um ein Echo w&auml;hrend der Passworteingabe zu unterdr&uuml;cken.
Ebenso werden alle zur&uuml;ckgegebenen Zeilen mit \r\n abgeschlossen.
Beispiel:<br>
<ul>
<code>
attr allowed_tPort password secret<br>
attr allowed_tPort password {"$password" eq "secret"}
</code>
</ul>
Hinweis: Falls dieses Attribut gesetzt wird, muss als erstes Argument
ein Passwort angegeben werden, wenn fhem.pl im Client-mode betrieben
wird:
<ul>
<code>
perl fhem.pl localhost:7072 secret "set lamp on"
</code>
</ul>
</li><br>
<a name="globalpassword"></a>
<li>globalpassword<br>
Betrifft nur telnet Instanzen (siehe validFor): Entspricht dem
Attribut password; ein Passwort wird aber ausschlie&szlig;lich f&uuml;r
nicht-lokale Verbindungen verlangt.
</li><br>
<a name="validFor"></a>
<li>validFor<br>
Komma separierte Liste von Forntend-Instanznamen. Aktuell werden nur
Frontends unterst&uuml;tzt, die das FHEM TCP/IP Bibliothek verwenden.
Falls gesetzt, dann gelten die mit den anderen Attributen
spezifizierten Regeln nur f&uuml;r diese Instanzen. Falls nicht
gesetzt, dann sind alle Frontends betroffen.
</li>
</ul>
<br>
</ul>
=end html_DE
=cut

View File

@ -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:
<ul>
<code>define tPort telnet 7072 global</code><br>
<code>attr tPort globalpassword mySecret</code><br>
<code>attr tPort SSL</code><br>
<code>attr allowed_tPort allowed</code><br>
<code>attr allowed_tPort validFor tPort</code><br>
<code>attr allowed_tPort globalpassword mySecret</code><br>
</ul>
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($;$)
<a name="telnetattr"></a>
<b>Attributes:</b>
<ul>
<a href="#allowedCommands">allowedCommands</a><br>
<a name="password"></a>
<li>password<br>
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:<br>
<ul>
<code>
attr tPort password secret<br>
attr tPort password {"$password" eq "secret"}
</code>
</ul>
Note: if this attribute is set, you have to specify a password as the
first argument when using fhem.pl in client mode:
<ul>
perl fhem.pl localhost:7072 secret "set lamp on"
</ul>
</li><br>
<a name="globalpassword"></a>
<li>globalpassword<br>
Just like the attribute password, but a password will only required for
non-local connections.
</li><br>
<a name="prompt"></a>
<li>prompt<br>
Sets the string for the telnet prompt, the default is fhem&gt;
@ -538,8 +516,10 @@ telnet_ActivateInform($;$)
Beispiele:
<ul>
<code>define tPort telnet 7072 global</code><br>
<code>attr tPort globalpassword mySecret</code><br>
<code>attr tPort SSL</code><br>
<code>attr allowed_tPort allowed</code><br>
<code>attr allowed_tPort validFor tPort</code><br>
<code>attr allowed_tPort globalpassword mySecret</code><br>
</ul>
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($;$)
<a name="telnetattr"></a>
<b>Attribute</b>
<ul>
<a href="#allowedCommands">allowedCommands</a><br>
<a name="password"></a>
<li>password<br>
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&uuml;ckgegebene Wert wahr (true), wurde das Passwort
akzeptiert. Falls dieser Parameter gesetzt wird, sendet fhem
telnet IAC Requests, um ein Echo w&auml;hrend der Passworteingabe zu
unterdr&uuml;cken. Ebenso werden alle zur&uuml;ckgegebenen Zeilen mit
\r\n abgeschlossen.
Beispiel:<br>
<ul>
<code>
attr tPort password secret<br>
attr tPort password {"$password" eq "secret"}
</code>
</ul>
Hinweis: Falls dieses Attribut gesetzt wird, muss als erstes Argument
ein Passwort angegeben werden, wenn fhem.pl im Client-mode betrieben
wird:
<ul>
<code>
perl fhem.pl localhost:7072 secret "set lamp on"
</code>
</ul>
</li><br>
<a name="globalpassword"></a>
<li>globalpassword<br>
Entspricht dem Attribut password; ein Passwort wird aber
ausschlie&szlig;lich f&uuml;r nicht-lokale Verbindungen verlangt.
</li><br>
<a name="prompt"></a>
<li>prompt<br>
Gibt die Zeichenkette an, welche in der Telnet-Sitzung als

View File

@ -93,6 +93,7 @@
<b>Helper modules</b>
<ul>
<a href="#at">at</a> &nbsp;
<a href="#allowed">at</a> &nbsp;
<a href="#autocreate">autocreate</a> &nbsp;
<a href="#average">average</a> &nbsp;
<a href="#Calendar">Calendar</a> &nbsp;

View File

@ -93,6 +93,7 @@
<b>Hilfs (Erweiterungs-) Module</b>
<ul>
<a href="#at">at</a> &nbsp;
<a href="#allowed">at</a> &nbsp;
<a href="#autocreate">autocreate</a> &nbsp;
<a href="#average">average</a> &nbsp;
<a href="#Calendar">Calendar</a> &nbsp;

View File

@ -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 <name> 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;