diff --git a/fhem/CHANGED b/fhem/CHANGED index 037525417..24d3556b4 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -17,6 +17,7 @@ - feature: new modules 66_ECMD.pm and 67_ECMDDevice.pm for ethersex-enabled devices and alike. - bugfix: serial port setting on Linux broken if running in the background + - feature: IPV6 support, FHEMWEB basicAuth and HTTPS support - 2010-08-15 (5.0) - **NOTE*: The default installation path is changed to satisfy lintian diff --git a/fhem/docs/commandref.html b/fhem/docs/commandref.html index b63421202..adb3826db 100644 --- a/fhem/docs/commandref.html +++ b/fhem/docs/commandref.html @@ -915,7 +915,11 @@ A line ending with \ will be concatenated with the next one, so long lines connections. To offer at least a little bit of security, the server will only listen for connections from the localhost per default. If there is a second value "global" then the server will listen for - non-localhost connections too. + non-localhost connections too.

+ To use IPV6, specify the port as IPV6:<number>, in this + case the perl module IO::Socket:INET6 will be requested. + On Linux you may have to install it with cpan -i IO::Socket::INET6 or + apt-get libio-socket-inet6-perl; the OSX perl already has this module.
@@ -5411,7 +5415,8 @@ Terminating

Enable the webfrontend on port <tcp-portnr>. If global is specified, then requests from all interfaces (not only localhost / 127.0.0.1) are - serviced. + serviced.
+ To enable listening on IPV6 see the comments here.
@@ -5493,6 +5498,44 @@ Terminating site on the iPhone in Safari, try to add it to the home-screen.
+ +
  • nofork
    + If set, do not generate the logs in a parallel process. Needed + on some Fritzbox installations. +

  • + + +
  • 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.: + 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 + browser when requesting the username/password. +

  • + + +
  • HTTPS
    + use HTTPS instead of HTTP. This feature requires the perl module + IO::Socket::SSL, to be installed with cpan -i IO::Socket::SSL or + apt-get install libio-socket-ssl-perl; the OSX perl already has this + module.
    + A local certificate has to be generated into a directory called certs, + this directory must be in the working directory (pwd) of fhem, + which is not necessarily the modpath directory. To generate it: + +

  • + + diff --git a/fhem/fhem.pl b/fhem/fhem.pl index 7656bf28d..533ed2b21 100755 --- a/fhem/fhem.pl +++ b/fhem/fhem.pl @@ -156,6 +156,7 @@ use vars qw($reread_active); my $AttrList = "room comment alias"; my $server; # Server socket +my $ipv6; # Using IPV6 my $currlogfile; # logfile, without wildcards my $logopened = 0; # logfile opened or using stdout my %client; # Client array @@ -166,7 +167,7 @@ my $nextat; # Time when next timer will be triggered. my $intAtCnt=0; my %duplicate; # Pool of received msg for multi-fhz/cul setups my $duplidx=0; # helper for the above pool -my $cvsid = '$Id: fhem.pl,v 1.128 2011-02-05 09:26:55 rudolfkoenig Exp $'; +my $cvsid = '$Id: fhem.pl,v 1.129 2011-02-05 19:04:19 rudolfkoenig Exp $'; my $namedef = "where is either:\n" . "- a single device name\n" . @@ -401,8 +402,12 @@ while (1) { Log 1, "Accept failed: $!"; next; } - my ($port, $iaddr) = sockaddr_in($clientinfo[1]); - my $caddr = inet_ntoa($iaddr); + my ($port, $iaddr) = $ipv6 ? + sockaddr_in6($clientinfo[1]) : + sockaddr_in($clientinfo[1]); + my $caddr = $ipv6 ? + inet_ntop(AF_INET6, $iaddr): + inet_ntoa($iaddr); my $af = $attr{global}{allowfrom}; if($af) { if(",$af," !~ m/,$caddr,/) { @@ -1535,13 +1540,27 @@ GlobalAttr($$) if($global && $global ne "global") { return "Bad syntax, usage: attr global port [global]"; } + if($port =~ m/^IPV6:(\d+)$/i) { + $port = $1; + $ipv6 = 1; + eval "require IO::Socket::INET6; use Socket6;"; + if($@) { + Log 1, $@; + Log 1, "Can't load INET6, falling back to IPV4"; + $ipv6 = 0; + } + } - my $server2 = IO::Socket::INET->new( - Proto => 'tcp', - LocalHost => ($global ? undef : "localhost"), - LocalPort => $port, - Listen => 10, - ReuseAddr => 1); + my $server2; + my @opts = ( + Domain => ($ipv6 ? AF_INET6 : AF_UNSPEC), # Linux bug + LocalHost => ($global ? undef : "localhost"), + LocalPort => $port, + Listen => 10, + ReuseAddr => 1 + ); + $server2 = $ipv6 ? IO::Socket::INET6->new(@opts) : + IO::Socket::INET->new(@opts); if(!$server2) { Log 1, "Can't open server port at $port: $!"; return "$!" if($init_done); diff --git a/fhem/webfrontend/pgm2/01_FHEMWEB.pm b/fhem/webfrontend/pgm2/01_FHEMWEB.pm index 78b531815..21a14edef 100755 --- a/fhem/webfrontend/pgm2/01_FHEMWEB.pm +++ b/fhem/webfrontend/pgm2/01_FHEMWEB.pm @@ -10,7 +10,6 @@ use IO::Socket; sub FW_digestCgi($); sub FW_doDetail($); sub FW_fileList($); -sub FW_getAttr($$$); sub FW_makeTable($$$$$$$$); sub FW_updateHashes(); sub FW_showRoom(); @@ -68,12 +67,12 @@ FHEMWEB_Initialize($) my ($hash) = @_; $hash->{ReadFn} = "FW_Read"; - + $hash->{AttrFn} = "FW_Attr"; $hash->{DefFn} = "FW_Define"; $hash->{UndefFn} = "FW_Undef"; $hash->{AttrList}= "loglevel:0,1,2,3,4,5,6 webname fwmodpath fwcompress " . "plotmode:gnuplot,gnuplot-scroll,SVG plotsize refresh " . - "smallscreen"; + "smallscreen nofork basicAuth basicAuthMsg HTTPS"; ############### # Initialize internal structures @@ -90,16 +89,30 @@ FW_Define($$) my ($hash, $def) = @_; my ($name, $type, $port, $global) = split("[ \t]+", $def); return "Usage: define FHEMWEB [global]" - if($port !~ m/^[0-9]+$/ || $port < 1 || $port > 65535 || - ($global && $global ne "global")); + if($port !~ m/^(IPV6:)?\d+$/ || ($global && $global ne "global")); + if($port =~ m/^IPV6:(\d+)$/i) { + $port = $1; + eval "require IO::Socket::INET6; use Socket6;"; + if($@) { + Log 1, $@; + Log 1, "Can't load INET6, falling back to IPV4"; + } else { + $hash->{IPV6} = 1; + } + } + + my @opts = ( + Domain => ($hash->{IPV6} ? AF_INET6 : AF_UNSPEC), # Linux bug + LocalHost => ($global ? undef : "localhost"), + LocalPort => $port, + Listen => 10, + ReuseAddr => 1 + ); $hash->{STATE} = "Initialized"; - $hash->{SERVERSOCKET} = IO::Socket::INET->new( - Proto => 'tcp', - LocalHost => (($global && $global eq "global") ? undef : "localhost"), - LocalPort => $port, - Listen => 10, - ReuseAddr => 1); + $hash->{SERVERSOCKET} = $hash->{IPV6} ? + IO::Socket::INET6->new(@opts) : + IO::Socket::INET->new(@opts); if(!$hash->{SERVERSOCKET}) { my $msg = "Can't open server port at $port: $!"; @@ -145,19 +158,22 @@ FW_Read($) if($hash->{SERVERSOCKET}) { # Accept and create a child - my @clientinfo = $hash->{SERVERSOCKET}->accept(); my $ll = GetLogLevel($name,4); - + my @clientinfo = $hash->{SERVERSOCKET}->accept(); if(!@clientinfo) { - Print("ERROR", 1, "016 Accept failed for admin port"); Log(1, "Accept failed for HTTP port ($name: $!)"); return; } - my @clientsock = sockaddr_in($clientinfo[1]); + my @clientsock = $hash->{IPV6} ? + sockaddr_in6($clientinfo[1]) : + sockaddr_in($clientinfo[1]); my %nhash; - my $cname = "FHEMWEB:". inet_ntoa($clientsock[1]) .":".$clientsock[0]; + my $cname = "FHEMWEB:". + ($hash->{IPV6} ? + inet_ntop(AF_INET6, $clientsock[1]) : + inet_ntoa($clientsock[1])) .":".$clientsock[0]; $nhash{NR} = $devcount++; $nhash{NAME} = $cname; $nhash{FD} = $clientinfo[0]->fileno(); @@ -172,15 +188,20 @@ FW_Read($) $defs{$nhash{NAME}} = \%nhash; $selectlist{$nhash{NAME}} = \%nhash; + if($hash->{SSL}) { + my $ret = IO::Socket::SSL->start_SSL($nhash{CD}, { SSL_server=>1, }); + Log 1, "SSL: $!" if(!$ret && $! ne "Socket is not connected"); + } + Log($ll, "Connection accepted from $nhash{NAME}"); return; - } $FW_wname = $hash->{SNAME}; my $ll = GetLogLevel($FW_wname,4); + my $c = $hash->{CD}; - if(!$zlib_loaded && FW_getAttr($FW_wname, "fwcompress", 1)) { + if(!$zlib_loaded && AttrVal($FW_wname, "fwcompress", 1)) { $zlib_loaded = 1; eval { require Compress::Zlib; }; if($@) { @@ -211,14 +232,32 @@ FW_Read($) #Log 0, "Got: >$hash->{BUF}<"; my @lines = split("[\r\n]", $hash->{BUF}); - my @enc = grep /Accept-Encoding/, @lines; + + ############################# + # BASIC HTTP AUTH + my $basicAuth = AttrVal($FW_wname, "basicAuth", undef); + if($basicAuth) { + my @auth = grep /^Authorization: Basic $basicAuth/, @lines; + if(!@auth) { + my $msg = AttrVal($FW_wname, "basicAuthMsg", "Fhem: login required"); + print $c "HTTP/1.1 401 Authorization Required\r\n", + "WWW-Authenticate: Basic realm=\"$msg\"\r\n", + "Content-Length: 0\r\n\r\n"; + return; + }; + } + ############################# + my @enc = grep /Accept-Encoding/, @lines; my ($mode, $arg, $method) = split(" ", $lines[0]); $hash->{BUF} = ""; Log($ll, "HTTP $name GET $arg"); my $pid; - return if(($arg =~ m/cmd=showlog/) && ($pid = fork)); + if(!AttrVal($FW_wname, "nofork", undef)) { + # Process SVG rendering as a parallel process + return if(($arg =~ m/cmd=showlog/) && ($pid = fork)); + } $hash->{INUSE} = 1; my $cacheable = FW_AnswerCall($arg); @@ -230,15 +269,16 @@ FW_Read($) } my $compressed = ""; - if(($FW_RETTYPE=~m/text/i || $FW_RETTYPE=~m/svg/i || $FW_RETTYPE=~m/script/i) && + if(($FW_RETTYPE=~m/text/i || + $FW_RETTYPE=~m/svg/i || + $FW_RETTYPE=~m/script/i) && (int(@enc) == 1 && $enc[0] =~ m/gzip/) && - FW_getAttr($FW_wname, "fwcompress", 1)) { + AttrVal($FW_wname, "fwcompress", 1)) { $FW_RET = Compress::Zlib::memGzip($FW_RET); $compressed = "Content-Encoding: gzip\r\n"; } - my $c = $hash->{CD}; my $length = length($FW_RET); my $expires = ($cacheable? ("Expires: ".localtime(time()+900)." GMT\r\n") : ""); @@ -259,9 +299,9 @@ FW_AnswerCall($) $FW_RET = ""; $FW_RETTYPE = "text/html; charset=ISO-8859-1"; - $FW_ME = "/" . FW_getAttr($FW_wname, "webname", "fhem"); - $FW_dir = FW_getAttr($FW_wname, "fwmodpath", "$attr{global}{modpath}/FHEM"); - $FW_ss = FW_getAttr($FW_wname, "smallscreen", 0); + $FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem"); + $FW_dir = AttrVal($FW_wname, "fwmodpath", "$attr{global}{modpath}/FHEM"); + $FW_ss = AttrVal($FW_wname, "smallscreen", 0); # Lets go: if($arg =~ m,^${FW_ME}/(.*html)$, || $arg =~ m,^${FW_ME}/(example.*)$,) { @@ -327,8 +367,8 @@ FW_AnswerCall($) $cmd !~ /^style / && $cmd !~ /^edit/); - $FW_plotmode = FW_getAttr($FW_wname, "plotmode", "SVG"); - $FW_plotsize = FW_getAttr($FW_wname, "plotsize", $FW_ss ? "480,160" : "800,160"); + $FW_plotmode = AttrVal($FW_wname, "plotmode", "SVG"); + $FW_plotsize = AttrVal($FW_wname, "plotsize", $FW_ss ? "480,160" : "800,160"); $FW_reldoc = "$FW_ME/commandref.html"; $FW_cmdret = $docmd ? fC($cmd) : ""; @@ -353,7 +393,7 @@ FW_AnswerCall($) } } - my $t = FW_getAttr("global", "title", "Home, Sweet Home"); + my $t = AttrVal("global", "title", "Home, Sweet Home"); pO ''; pO ''; @@ -365,7 +405,7 @@ FW_AnswerCall($) pO ''; } - my $rf = FW_getAttr($FW_wname, "refresh", ""); + my $rf = AttrVal($FW_wname, "refresh", ""); pO "" if($rf); my $stylecss = ($FW_ss ? "style_smallscreen.css" : "style.css"); pO ""; @@ -442,7 +482,7 @@ FW_updateHashes() %FW_rooms = (); foreach my $d (keys %defs ) { next if(IsIgnored($d)); - foreach my $r (split(",", FW_getAttr($d, "room", "Unsorted"))) { + foreach my $r (split(",", AttrVal($d, "room", "Unsorted"))) { $FW_rooms{$r}{$d} = 1; } } @@ -458,7 +498,7 @@ FW_updateHashes() $FW_types{$t}{$d} = 1; } - $FW_room = FW_getAttr($FW_detail, "room", "Unsorted") if($FW_detail); + $FW_room = AttrVal($FW_detail, "room", "Unsorted") if($FW_detail); } ############################## @@ -536,7 +576,7 @@ FW_showArchive($) if($fn =~ m,^(.+)/([^/]+)$,) { $fn = $2; } - $fn = FW_getAttr($d, "archivedir", "") . "/" . $fn; + $fn = AttrVal($d, "archivedir", "") . "/" . $fn; my $t = $defs{$d}{TYPE}; pO "
    "; @@ -544,7 +584,7 @@ FW_showArchive($) pO "", $row?"odd":"even"; $row = ($row+1)%2; @@ -575,7 +615,7 @@ FW_doDetail($) pO ""; pO FW_hidden("detail", $d); - $FW_room = FW_getAttr($d, "room", undef); + $FW_room = AttrVal($d, "room", undef); my $t = $defs{$d}{TYPE}; pO "
    "; @@ -782,7 +822,7 @@ FW_showRoom() my $iv = $v; # icon value my $iname = ""; - if(defined(FW_getAttr($d, "showtime", undef))) { + if(defined(AttrVal($d, "showtime", undef))) { $v = $defs{$d}{READINGS}{state}{TIME}; @@ -835,7 +875,7 @@ FW_showRoom() pH "detail=$d", $d, 1; pO "
    "; - if(defined(FW_getAttr($d, "archivedir", undef))) { + if(defined(AttrVal($d, "archivedir", undef))) { pH "cmd=showarchive $d", "archive", 1; } @@ -843,7 +883,7 @@ FW_showRoom() pF " "; pF " ", $row?"odd":"even"; $row = ($row+1)%2; - foreach my $ln (split(",", FW_getAttr($d, "logtype", "text"))) { + foreach my $ln (split(",", AttrVal($d, "logtype", "text"))) { my ($lt, $name) = split(":", $ln); $name = $lt if(!$name); pH "cmd=logwrapper $d $lt $f", $name, 1; @@ -904,7 +944,7 @@ FW_logWrapper($) if($type eq "text") { $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File my $path = "$1/$file"; - $path = FW_getAttr($d,"archivedir","") . "/$file" if(!-f $path); + $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path); if(!open(FH, $path)) { pO "
    $path: $!
    "; @@ -927,8 +967,8 @@ FW_logWrapper($) pO "
    "; my $row = 0; - my $l = FW_getAttr($d, "logtype", undef); + my $l = AttrVal($d, "logtype", undef); foreach my $f (FW_fileList($fn)) { pF "
    $f
    $v
    $f
    "; pO ""; my $arg = "$FW_ME?cmd=showlog undef $d $type $file"; - if(FW_getAttr($d,"plotmode",$FW_plotmode) eq "SVG") { - my ($w, $h) = split(",", FW_getAttr($d,"plotsize",$FW_plotsize)); + if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { + my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); pO "\n"; @@ -979,9 +1019,9 @@ FW_substcfg($$$$$$) my $oll = $attr{global}{verbose}; $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - my $title = FW_getAttr($wl, "title", "\"$file\""); + my $title = AttrVal($wl, "title", "\"$file\""); $title = AnalyzeCommand(undef, "{ $title }"); - my $label = FW_getAttr($wl, "label", undef); + my $label = AttrVal($wl, "label", undef); my @g_label; if ($label) { @g_label = split(":",$label); @@ -996,7 +1036,7 @@ FW_substcfg($$$$$$) $gplot_script =~ s//$tmpfile/g; - my $ps = FW_getAttr($wl,"plotsize",$FW_plotsize); + my $ps = AttrVal($wl,"plotsize",$FW_plotsize); $gplot_script =~ s//$ps/g; $gplot_script =~ s//$title/g; @@ -1029,7 +1069,7 @@ FW_showLog($) my ($cmd) = @_; my (undef, $wl, $d, $type, $file) = split(" ", $cmd, 5); - my $pm = FW_getAttr($wl,"plotmode",$FW_plotmode); + my $pm = AttrVal($wl,"plotmode",$FW_plotmode); my $gplot_pgm = "$FW_dir/$type.gplot"; @@ -1061,14 +1101,14 @@ FW_showLog($) # Looking for the logfile.... $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File my $path = "$1/$file"; - $path = FW_getAttr($d,"archivedir","") . "/$file" if(!-f $path); + $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path); return FW_fatal("Cannot read $path") if(!-r $path); my ($err, $cfg, $plot, undef) = FW_readgplotfile($wl, $gplot_pgm, $file); return $err if($err); my $gplot_script = FW_substcfg(0, $wl, $cfg, $plot, $file,$tmpfile); - my $fr = FW_getAttr($wl, "fixedrange", undef); + my $fr = AttrVal($wl, "fixedrange", undef); if($fr) { $fr =~ s/ /\":\"/; $fr = "set xrange [\"$fr\"]\n"; @@ -1278,7 +1318,7 @@ FW_calcWeblink($$) { my ($d,$wl) = @_; - my $pm = FW_getAttr($d,"plotmode",$FW_plotmode); + my $pm = AttrVal($d,"plotmode",$FW_plotmode); return if($pm eq "gnuplot"); if(!$d) { @@ -1288,7 +1328,7 @@ FW_calcWeblink($$) next if($defs{$d}{WLTYPE} ne "fileplot"); next if(!$FW_room || ($FW_room ne "all" && !$FW_rooms{$FW_room}{$d})); - next if(FW_getAttr($d, "fixedrange", undef)); + next if(AttrVal($d, "fixedrange", undef)); next if($pm eq "gnuplot"); $cnt++; } @@ -1297,7 +1337,7 @@ FW_calcWeblink($$) return if(!$defs{$wl}); - my $fr = FW_getAttr($wl, "fixedrange", undef); + my $fr = AttrVal($wl, "fixedrange", undef); my $frx; if($fr) { #klaus fixed range day, week, month or year @@ -1516,15 +1556,6 @@ fC($) return $ret; } -################## -sub -FW_getAttr($$$) -{ - my ($d, $aname, $def) = @_; - return $attr{$d}{$aname} if($d && $attr{$d} && defined($attr{$d}{$aname})); - return $def; -} - ################## sub FW_showWeblink($$$) @@ -1559,8 +1590,8 @@ FW_showWeblink($$$) my $wl = "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); my $arg="$FW_ME?cmd=showlog $d $va[0] $va[1] $va[2]$wl"; - if(FW_getAttr($d,"plotmode",$FW_plotmode) eq "SVG") { - my ($w, $h) = split(",", FW_getAttr($d,"plotsize",$FW_plotsize)); + if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { + my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); pO "\n"; @@ -1580,4 +1611,21 @@ FW_showWeblink($$$) } } +sub +FW_Attr(@) +{ + my @a = @_; + my $hash = $defs{$a[1]}; + + if($a[0] eq "set" && $a[2] eq "HTTPS") { + eval "require IO::Socket::SSL"; + if($@) { + Log 1, $@; + Log 1, "Can't load IO::Socket::SSL, falling back to HTTP"; + } else { + $hash->{SSL} = 1; + } + } + return undef; +} 1;