############################################## # $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 ". "basicAuthExpiry"; $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 0: allow stacking with other instances, see Forum#46380 return ($me->{allowedCommands} =~ m/\b$arg\b/) ? 0 : 2; } if($type eq "devicename") { return 0 if(!$me->{allowedDevices}); return ($me->{allowedDevices} =~ m/\b$arg\b/) ? 0 : 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); delete $cl->{".httpAuthHeader"}; return 0 if(!$basicAuth); my $FW_httpheader = $param; my $secret = $FW_httpheader->{Authorization}; $secret =~ s/^Basic //i if($secret); # Check for Cookie in headers if no basicAuth header is set my $authcookie; if ( ( ! $secret ) && ( $FW_httpheader->{Cookie} ) ) { if ( AttrVal($aName, "basicAuthExpiry", 0)) { my $cookie = "; ".$FW_httpheader->{Cookie}.";"; $authcookie = $1 if ( $cookie =~ /; AuthToken=([^;]+);/ ); $secret = $authcookie; } } 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($@); } } # Add Cookie header ONLY if # authentication with basic auth was succesful # (meaning if no or wrong authcookie set) if ( ( $pwok ) && ( ( ! defined($authcookie) ) || ( $secret ne $authcookie ) ) ) { # no cookie set but authorization succesful # check if cookie should be set --> Cookie Attribute != 0 my $time = int(AttrVal($aName, "basicAuthExpiry", 0)); if ( $time ) { # time specified in days until next expiry (but timestamp is in seconds) $time *= 86400; $time += time(); # generate timestamp according to RFC-1130 in Expires my $expires = "Expires=".FmtDateTimeRFC1123($time); # set header with expiry $cl->{".httpAuthHeader"} = "Set-Cookie: AuthToken=".$secret. "; Path=/ ; ".$expires."\r\n" ; } } 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/_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 =item helper =begin html

allowed

=end html =begin html_DE

allowed

=end html_DE =cut