telnet client mode

git-svn-id: https://svn.fhem.de/fhem/trunk@2463 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2013-01-10 14:40:57 +00:00
parent 73d45dd278
commit 9e8332ae5a
3 changed files with 205 additions and 49 deletions

View File

@ -48,6 +48,7 @@
OWFS OWFS
- feature: stateFormat (readingsFn modules) and showInternalValues attributes - feature: stateFormat (readingsFn modules) and showInternalValues attributes
- feature: new readingsFn modules: FS20 CUL_WS HMS CUL_EM CUL_TX EnOcean ZWave - feature: new readingsFn modules: FS20 CUL_WS HMS CUL_EM CUL_TX EnOcean ZWave
- feature: telnet client mode
- 2012-10-28 (5.3) - 2012-10-28 (5.3)
- feature: added functions trim, ltrim, rtrim, UntoggleDirect, - feature: added functions trim, ltrim, rtrim, UntoggleDirect,

View File

@ -21,7 +21,7 @@ telnet_Initialize($)
$hash->{AttrFn} = "telnet_Attr"; $hash->{AttrFn} = "telnet_Attr";
$hash->{NotifyFn}= "telnet_SecurityCheck"; $hash->{NotifyFn}= "telnet_SecurityCheck";
$hash->{AttrList} = "loglevel:0,1,2,3,4,5,6 globalpassword password ". $hash->{AttrList} = "loglevel:0,1,2,3,4,5,6 globalpassword password ".
"allowfrom SSL"; "allowfrom SSL connectTimeout connectInterval";
} }
##################################### #####################################
@ -45,6 +45,63 @@ telnet_SecurityCheck($$)
return; return;
} }
##########################
sub
telnet_ClientConnect($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
$hash->{DEF} =~ m/^(IPV6:)?(.*):(\d+)$/;
my ($isIPv6, $server, $port) = ($1, $2, $3);
Log GetLogLevel($name,4), "$name: Connecting to $server:$port...";
my @opts = (
PeerAddr => "$server:$port",
Timeout => AttrVal($name, "connectTimeout", 2),
);
my $client;
if($hash->{SSL}) {
$client = IO::Socket::SSL->new(@opts);
} else {
$client = IO::Socket::INET->new(@opts);
}
if($client) {
$hash->{FD} = $client->fileno();
$hash->{CD} = $client; # sysread / close won't work on fileno
$hash->{BUF} = "";
$hash->{CONNECTS}++;
$selectlist{$name} = $hash;
$hash->{STATE} = "Connected";
RemoveInternalTimer($hash);
Log(GetLogLevel($name,3), "$name: connected to $server:$port");
} else {
telnet_ClientDisconnect($hash, 1);
}
}
##########################
sub
telnet_ClientDisconnect($$)
{
my ($hash, $connect) = @_;
my $name = $hash->{NAME};
close($hash->{CD}) if($hash->{CD});
delete($hash->{FD});
delete($hash->{CD});
delete($selectlist{$name});
$hash->{STATE} = "Disconnected";
InternalTimer(gettimeofday()+AttrVal($name, "connectInterval", 60),
"telnet_ClientConnect", $hash, 0);
if($connect) {
Log GetLogLevel($name,4), "$name: Connect failed.";
} else {
Log GetLogLevel($name,3), "$name: Disconnected";
}
}
########################## ##########################
sub sub
telnet_Define($$$) telnet_Define($$$)
@ -53,18 +110,30 @@ telnet_Define($$$)
my @a = split("[ \t][ \t]*", $def); my @a = split("[ \t][ \t]*", $def);
my ($name, $type, $port, $global) = split("[ \t]+", $def); my ($name, $type, $port, $global) = split("[ \t]+", $def);
return "Usage: define <name> telnet [IPV6:]<tcp-portnr> [global]"
if($port !~ m/^(IPV6:)?\d+$/ || ($global && $global ne "global"));
my $ret = TcpServer_Open($hash, $port, $global); my $isServer = 1 if($port && $port =~ m/^(IPV6:)?\d+$/);
my $isClient = 1 if($port && $port =~ m/^(IPV6:)?.*:\d+$/);
return "Usage: define <name> telnet { [IPV6:]<tcp-portnr> [global] | ".
" [IPV6:]serverName:port }"
if(!($isServer || $isClient) ||
($isClient && $global) ||
($global && $global ne "global"));
# Make sure that fhem only runs once # Make sure that fhem only runs once
if($ret && !$init_done) { if($isServer) {
Log 1, "$ret. Exiting."; my $ret = TcpServer_Open($hash, $port, $global);
exit(1); if($ret && !$init_done) {
Log 1, "$ret. Exiting.";
exit(1);
}
return $ret;
} }
return $ret;
if($isClient) {
$hash->{isClient} = 1;
telnet_ClientConnect($hash);
}
} }
sub sub
@ -97,16 +166,22 @@ telnet_Read($)
my $buf; my $buf;
my $ret = sysread($hash->{CD}, $buf, 256); my $ret = sysread($hash->{CD}, $buf, 256);
if(!defined($ret) || $ret <= 0) { if(!defined($ret) || $ret <= 0) {
CommandDelete(undef, $name); if($hash->{isClient}) {
telnet_ClientDisconnect($hash, 0);
} else {
CommandDelete(undef, $name);
}
return; return;
} }
if(ord($buf) == 4) { # EOT / ^D if(ord($buf) == 4) { # EOT / ^D
CommandQuit($hash, ""); CommandQuit($hash, "");
return; return;
} }
$buf =~ s/\r//g; $buf =~ s/\r//g;
my $pw = telnet_pw($hash->{SNAME}, $name); my $sname = ($hash->{isClient} ? $name : $hash->{SNAME});
my $pw = telnet_pw($sname, $name);
if($pw) { if($pw) {
$buf =~ s/\xff..//g; # Telnet IAC stuff $buf =~ s/\xff..//g; # Telnet IAC stuff
$buf =~ s/\xfd(.)//; # Telnet Do ? $buf =~ s/\xfd(.)//; # Telnet Do ?
@ -136,7 +211,12 @@ telnet_Read($)
$hash->{pwEntered} = 1; $hash->{pwEntered} = 1;
next; next;
} else { } else {
CommandDelete(undef, $name); if($hash->{isClient}) {
telnet_ClientDisconnect($hash, 0);
} else {
delete($hash->{rcvdQuit});
CommandDelete(undef, $name);
}
return; return;
} }
} }
@ -175,8 +255,17 @@ telnet_Read($)
last if(!$l || $l == length($ret)); last if(!$l || $l == length($ret));
$ret = substr($ret, $l); $ret = substr($ret, $l);
} }
$hash->{CD}->flush();
}
if($hash->{rcvdQuit}) {
if($hash->{isClient}) {
delete($hash->{rcvdQuit});
telnet_ClientDisconnect($hash, 0);
} else {
CommandDelete(undef, $name);
}
} }
CommandDelete(undef, $name) if($hash->{rcvdQuit});
} }
########################## ##########################
@ -188,6 +277,10 @@ telnet_Attr(@)
if($a[0] eq "set" && $a[2] eq "SSL") { if($a[0] eq "set" && $a[2] eq "SSL") {
TcpServer_SetSSL($hash); TcpServer_SetSSL($hash);
if($hash->{CD}) {
my $ret = IO::Socket::SSL->start_SSL($hash->{CD});
Log 1, "$hash->{NAME} start_SSL: $ret" if($ret);
}
} }
return undef; return undef;
} }
@ -211,30 +304,48 @@ telnet_Undef($$)
<a name="telnetdefine"></a> <a name="telnetdefine"></a>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; telnet &lt;portNumber&gt; [global]</code> <code>define &lt;name&gt; telnet &lt;portNumber&gt; [global]</code><br>
or<br>
<code>define &lt;name&gt; telnet &lt;servername&gt:&lt;portNumber&gt;</code>
<br><br> <br><br>
First form, <b>server</b> mode:<br>
Listen on the TCP/IP port <code>&lt;portNumber&gt;</code> for incoming Listen on the TCP/IP port <code>&lt;portNumber&gt;</code> for incoming
connections. If the second parameter global is <b>not</b> specified, connections. If the second parameter global is <b>not</b> specified,
the server will only listen to localhost connections. the server will only listen to localhost connections.
<br><br> <br>
To use IPV6, specify the portNumber as IPV6:&lt;number&gt;, in this To use IPV6, specify the portNumber as IPV6:&lt;number&gt;, in this
case the perl module IO::Socket:INET6 will be requested. 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 On Linux you may have to install it with cpan -i IO::Socket::INET6 or
apt-get libio-socket-inet6-perl; OSX and the FritzBox-7390 perl already has apt-get libio-socket-inet6-perl; OSX and the FritzBox-7390 perl already has
this module. this module.<br>
<br><br>
Examples: Examples:
<ul> <ul>
<code>define tPort telnet 7072 global</code><br> <code>define tPort telnet 7072 global</code><br>
<code>attr tPort globalpassword mySecret</code><br> <code>attr tPort globalpassword mySecret</code><br>
<code>attr tPort SSL</code><br> <code>attr tPort SSL</code><br>
</ul> </ul>
<br>
Note: The old global attribute port is automatically converted to a Note: The old global attribute port is automatically converted to a
telnet instance with the name telnetPort. The global allowfrom attibute is telnet instance with the name telnetPort. The global allowfrom attibute is
lost in this conversion. lost in this conversion.
<br><br>
Second form, <b>client</b> mode:<br>
Connect to the specified server port, and execute commands received from
there just like in server mode. This can be used to connect to a fhem
instance sitting behind a firewall, when installing exceptions in the
firewall is not desired or possible. Note: this client mode supprts SSL,
but not IPV6.<br>
Example:
<ul>
Start tcptee first on publicly reachable host outside the firewall.<ul>
perl contrib/tcptee.pl --bidi 3000</ul>
Configure fhem inside the firewall:<ul>
define tClient telnet &lt;tcptee_host&gt;:3000</ul>
Connect to the fhem from outside of the firewall:<ul>
telnet &lt;tcptee_host&gt; 3000</ul>
</ul>
</ul> </ul>
<br> <br>
@ -291,6 +402,19 @@ telnet_Undef($$)
only connections from these addresses are allowed. only connections from these addresses are allowed.
</li><br> </li><br>
<a name="connectTimeout"></a>
<li>connectTimeout<br>
Wait at maximum this many seconds for the connection to be established.
Default is 2.
</li><br>
<a name="connectInterval"></a>
<li>connectInterval<br>
After closing a connection, or if a connection cannot be estblished,
try to connect again after this many seconds. Default is 60.
</li><br>
</ul> </ul>
</ul> </ul>

View File

@ -10,10 +10,11 @@ my $bidi;
my $loop; my $loop;
my $myIp; my $myIp;
my $myPort; my $myPort;
my $ssl;
my $serverHost; my $serverHost;
my $serverPort; my $serverPort;
my $usage = "Usage: tcptee.pl [--bidi] [--loop] " . my $usage = "Usage: tcptee.pl [--bidi] [--loop] [--ssl] " .
"[myIp:]myPort:serverHost:serverPort\n"; "[myIp:]myPort[:serverHost:serverPort]\n";
while(@ARGV) { while(@ARGV) {
my $opt = shift @ARGV; my $opt = shift @ARGV;
@ -24,13 +25,19 @@ while(@ARGV) {
} elsif($opt =~ m/^--loop$/i) { } elsif($opt =~ m/^--loop$/i) {
$loop = 1 $loop = 1
} elsif($opt =~ m/^(.*):(\d+):(.*):(\d+)/) { } elsif($opt =~ m/^--ssl$/i) {
$ssl = 1
} elsif($opt =~ m/^(\d+)$/) {
$myPort = $opt;
} elsif($opt =~ m/^(.*):(\d+):(.*):(\d+)$/) {
$myIp = $1; $myIp = $1;
$myPort = $2; $myPort = $2;
$serverHost = $3; $serverHost = $3;
$serverPort = $4; $serverPort = $4;
} elsif($opt =~ m/^(\d+):(.*):(\d+)/) { } elsif($opt =~ m/^(\d+):(.*):(\d+)$/) {
$myPort = $1; $myPort = $1;
$serverHost = $2; $serverHost = $2;
$serverPort = $3; $serverPort = $3;
@ -41,10 +48,10 @@ while(@ARGV) {
} }
} }
die $usage if(!$serverHost);
my ($sfd, $myfd, %clients, $discoMsg); my ($sfd, $myfd, %clients, $discoMsg);
die $usage if(!$myPort);
sub sub
tPrint($) tPrint($)
{ {
@ -58,16 +65,18 @@ tPrint($)
for(;;) { for(;;) {
# Open the server first # Open the server first
$sfd = IO::Socket::INET->new(PeerAddr => "$serverHost:$serverPort"); if($serverHost) {
if(!$sfd) { $sfd = IO::Socket::INET->new(PeerAddr => "$serverHost:$serverPort");
tPrint "Cannot connect to $serverHost:$serverPort : $!" if(!$discoMsg); if(!$sfd) {
tPrint "Cannot connect to $serverHost:$serverPort : $!" if(!$discoMsg);
$discoMsg = 1;
last if(!$loop);
sleep(5);
next;
}
$discoMsg = 1; $discoMsg = 1;
last if(!$loop); tPrint "Connected to $serverHost:$serverPort";
sleep(5);
next;
} }
$discoMsg = 1;
tPrint "Connected to $serverHost:$serverPort";
# Now open our listener # Now open our listener
@ -85,7 +94,7 @@ for(;;) {
# Data loop # Data loop
for(;;) { for(;;) {
my ($rin,$rout) = ('',''); my ($rin,$rout) = ('','');
vec($rin, $sfd->fileno(), 1) = 1; vec($rin, $sfd->fileno(), 1) = 1 if($sfd);
vec($rin, $myfd->fileno(), 1) = 1; vec($rin, $myfd->fileno(), 1) = 1;
foreach my $c (keys %clients) { foreach my $c (keys %clients) {
vec($rin, fileno($clients{$c}{fd}), 1) = 1; vec($rin, fileno($clients{$c}{fd}), 1) = 1;
@ -110,11 +119,29 @@ for(;;) {
$clients{$fd}{addr} = inet_ntoa($iaddr) . ":$port"; $clients{$fd}{addr} = inet_ntoa($iaddr) . ":$port";
tPrint "Connection accepted from $clients{$fd}{addr}"; tPrint "Connection accepted from $clients{$fd}{addr}";
if($ssl) {
tPrint "Attaching SSL";
eval "require IO::Socket::SSL";
if($@) {
tPrint "Can't load IO::Socket::SSL, falling back to plain";
} else {
my $ret = IO::Socket::SSL->start_SSL($fd, {
SSL_server => 1,
SSL_key_file => "certs/server-key.pem",
SSL_cert_file => "certs/server-cert.pem",
});
if(!$ret && $! ne "Socket is not connected") {
die "SSL/HTTPS error: $!";
}
}
}
syswrite($fd, $firstmsg) if($firstmsg); syswrite($fd, $firstmsg) if($firstmsg);
} }
# Data from the server # Data from the server
if(vec($rout, $sfd->fileno(), 1)) { if($sfd && vec($rout, $sfd->fileno(), 1)) {
my $buf; my $buf;
my $ret = sysread($sfd, $buf, 256); my $ret = sysread($sfd, $buf, 256);
if(!defined($ret) || $ret <= 0) { if(!defined($ret) || $ret <= 0) {
@ -123,33 +150,37 @@ for(;;) {
} }
foreach my $c (keys %clients) { foreach my $c (keys %clients) {
syswrite($clients{$c}{fd}, $buf); syswrite($clients{$c}{fd}, $buf);
$clients{$c}{fd}->flush();
} }
$firstmsg = $buf if(!$firstmsg); $firstmsg = $buf if(!$firstmsg);
} }
# Data from one of the clients # Data from one of the clients
foreach my $c (keys %clients) { CLIENT:foreach my $c (keys %clients) {
next if(!vec($rout, fileno($clients{$c}{fd}), 1)); next if(!vec($rout, fileno($clients{$c}{fd}), 1));
my $buf; for(;;) {
my $ret = sysread($clients{$c}{fd}, $buf, 256); my $buf;
if(!defined($ret) || $ret <= 0) { my $ret = sysread($clients{$c}{fd}, $buf, 256);
close($clients{$c}{fd}); if(!defined($ret) || $ret <= 0) {
tPrint "Client $clients{$c}{addr} left us"; close($clients{$c}{fd});
delete($clients{$c}); tPrint "Client $clients{$c}{addr} left us";
next; delete($clients{$c});
} next CLIENT;
syswrite($sfd, $buf);
if($bidi) {
foreach my $c2 (keys %clients) {
syswrite($clients{$c2}{fd}, $buf) if($c2 ne $c);
} }
syswrite($sfd, $buf) if($sfd);
if($bidi) {
foreach my $c2 (keys %clients) {
syswrite($clients{$c2}{fd}, $buf) if($c2 ne $c);
}
}
last if(!$ssl || !$clients{$c}{fd}->pending());
} }
} }
} }
close($sfd); close($sfd) if($sfd);
close($myfd); close($myfd);
foreach my $c (keys %clients) { foreach my $c (keys %clients) {
close($clients{$c}{fd}); close($clients{$c}{fd});