From ff2b6e4ea2ea1b88c461f01966ab99bbde684f2f Mon Sep 17 00:00:00 2001 From: bios01 <> Date: Thu, 17 Sep 2015 09:25:53 +0000 Subject: [PATCH] 70_Jabber.pm: - Added OTR (Off the Record) end to end encryption - Added MUC (Multi-User-Channel) joining and handling git-svn-id: https://svn.fhem.de/fhem/trunk@9265 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/70_Jabber.pm | 824 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 749 insertions(+), 77 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 9a87e6cc7..b5feb70d2 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # 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: 70_Jabber: - Added OTR (Off the Record) end to end encryption + - Added MUC (Multi-User-Channel) joining and handling - change: 74_AMAD: improved translation englisch - bugfix: 95_Dashboard: fixed access to uninitialized value - change: 74_AMAD: change set of STATE has been replaced by Readingsupdate diff --git a/fhem/FHEM/70_Jabber.pm b/fhem/FHEM/70_Jabber.pm index 34b96d69f..3fdda7a49 100644 --- a/fhem/FHEM/70_Jabber.pm +++ b/fhem/FHEM/70_Jabber.pm @@ -22,9 +22,11 @@ # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # -# Version: 1.4 - 2015-08-27 +# Version: 1.5 - 2015-09-17 # # Changelog: +# v1.5 2015-09-17 Added OTR (Off the Record) end to end encryption +# Added MUC (Multi-User-Channel) joining and handling # v1.4 2015-08-27 Fixed broken callback registration in Net::XMPP >= 1.04 # v1.3 2015-01-10 Fixed DNS SRV resolving and resulting wrong to: address # v1.2 2015-01-09 hardening XML::Stream Process() call and fix of ssl_verify @@ -39,6 +41,8 @@ # For using the SSL features and to connect securly to a Jabber server you also need this perl Module: # Net::SSLeay # +# For using OTR you need to compile Crypt::OTR from CPAN on your own +# # The recommended debian packages to be installed are these: # libnet-jabber-perl libnet-xmpp-perl libxml-stream-perl libdigest-sha1-perl libauthen-sasl-perl libnet-ssleay-perl # @@ -51,6 +55,8 @@ use warnings; use utf8; use Time::HiRes qw(gettimeofday); use Net::Jabber; +use base qw( Net::XMPP::Namespaces ); +use Blocking; sub Jabber_Set($@); sub Jabber_Define($$); @@ -64,6 +70,8 @@ my $debug = 0; my %sets = ( "msg" => 1, + "msgmuc" => 1, + "msgotr" => 1, "subscribe" => 1 ); @@ -77,7 +85,7 @@ Jabber_Initialize($) $hash->{DefFn} = "Jabber_Define"; $hash->{UndefFn} = "Jabber_UnDef"; $hash->{AttrFn} = "Jabber_Attr"; - $hash->{AttrList} = "dummy:1,0 loglevel:0,1,2,3,4,5 OnlineStatus:available,unavailable PollTimer RecvWhitelist ResourceName ".$readingFnAttributes; + $hash->{AttrList} = "dummy:1,0 loglevel:0,1,2,3,4,5 OnlineStatus:available,unavailable PollTimer RecvWhitelist ResourceName MucJoin MucRecvWhitelist OTREnable OTRSharedSecret ".$readingFnAttributes; } ################################### @@ -95,13 +103,25 @@ sub Jabber_Set($@) return Jabber_Set_Message($hash, @args); } + if ($cmd eq 'msgmuc') + { + return Jabber_Set_MUCMessage($hash, @args); + } + + if ($cmd eq 'msgotr') + { + return Jabber_Set_OTRMessage($hash, @args); + } + + if ($cmd eq 'subscribe') { return Jabber_Subcribe_To($hash, @args); } } - +################################### +# Set's ################################### sub Jabber_Set_Message($@) { @@ -109,14 +129,64 @@ sub Jabber_Set_Message($@) my $message = join(" ", @tmpMsg); utf8::decode($message); if (Jabber_CheckConnection($hash)) { + $hash->{JabberDevice}->MessageSend(to=>$dst, - subject=>"", body=>$message, type=>"chat", priority=>10); } } +################################### +sub Jabber_Set_MUCMessage($@) +{ + my ($hash,$dst,@tmpMsg) = @_; + my $message = join(" ", @tmpMsg); + utf8::decode($message); + if (Jabber_CheckConnection($hash)) { + + #convert the groupchat id to a short id else this would be a private message which is not allowed via "groupchat" type + my $JID = new Net::Jabber::JID($dst); + my $senderShort = $JID->GetJID("base"); + $hash->{JabberDevice}->MessageSend(to=>$senderShort, + body=>$message, + type=>"groupchat", + priority=>10); + } +} + +################################### +sub Jabber_Set_OTRMessage($@) +{ + my ($hash,$dst,@tmpMsg) = @_; + my $message = join(" ", @tmpMsg); + utf8::decode($message); + if (Jabber_CheckConnection($hash)) { + if ($hash->{helper}{otractive}) { + my $JID = new Net::Jabber::JID($dst); + + if (defined($hash->{helper}{otrJIDs}{$JID->GetJID("full")}) && defined($hash->{helper}{otrJIDs}{$JID->GetJID("full")}{verified}) ) { + #send a encrypted message as we have an connection + if (my $ciphertext = $hash->{OTR}->encrypt($JID->GetJID("full"), $message)) { + Log 0, "$hash->{NAME} Secure sending to ".$JID->GetJID("full") if $debug; + Jabber_Set_Message($hash,$dst,$ciphertext); + } else { + Log 0, "$hash->{NAME} Your message was not sent - no encrypted conversation is established" if $debug; + } + } else { + #establish a secure connection and send the message then. + Log 0, "$hash->{NAME} No secure connection to ".$JID->GetJID("full")." - establishing and sending message later..." if $debug; + #send it later + push @{ $hash->{helper}{otrJIDs}{$JID->GetJID("full")}{waitingMsgs} }, {"jid" => $JID->GetJID("full"), "msg" => $message} ; + #establish... + $hash->{OTR}->establish($JID->GetJID("full")); + } + } else { + return "OTR not activated. Activate by using 'attr $hash->{NAME} OTREnable 1'"; + } + } +} + ################################### sub Jabber_Subcribe_To($@) { @@ -161,6 +231,9 @@ Jabber_Define($$) $hash->{helper}{port} = $port; $hash->{helper}{tls} = $tls; $hash->{helper}{ssl} = $ssl; + $hash->{helper}{otractive} = 0; + $hash->{helper}{otrJIDs} = {}; #hash + if ($tls == 1 || $ssl == 1) { if(!eval("require Net::SSLeay;")) { $hash->{STATE} = "Disconnected (Module error)"; @@ -194,6 +267,7 @@ Jabber_UnDef($$) } ################################### +# Attrib sub Jabber_Attr(@) { @@ -202,22 +276,92 @@ Jabber_Attr(@) # $cmd can be "del" or "set" # $name is device name # aName and aVal are Attribute name and value - if ($cmd eq "set") { - if ($aName eq "OnlineStatus") { - if (defined($aVal) && defined($hash->{JabberDevice}) && $init_done) { - #Send Presence type only if we do not want to be available - if ($aVal ne "available") { - $hash->{JabberDevice}->PresenceSend(type=>$aVal); - } else { - $hash->{JabberDevice}->PresenceSend(); - } - } - } - } + if ($cmd eq "set") { + if ($aName eq "OnlineStatus") { + if (defined($aVal) && defined($hash->{JabberDevice}) && $init_done) { + #Send Presence type only if we do not want to be available + if ($aVal ne "available") { + $hash->{JabberDevice}->PresenceSend(type=>$aVal); + } else { + $hash->{JabberDevice}->PresenceSend(); + } + } + } elsif ($aName eq "MucJoin") { + #Join the MUC + if (defined($aVal) && defined($hash->{JabberDevice}) && $init_done) { + Jabber_MUCs_Join($hash,$aVal); + } + } elsif ($aName eq "OTREnable") { + #We dont care if Jabber is not connected already + if (defined($aVal) && $init_done) { + #OTR Enabled, init OTR + if ($aVal == 1) { + Jabber_OTR_Init($hash); + } + } + } elsif ($aName eq "OTRSharedSecret") { + #Nothing special to do will be used later.. + } + } return undef; } -################################### +########################################## +# Joins a MUC and save the nick/name for later processing +sub +Jabber_MUCs_Join($$) +{ + my ($hash,$MUCJID) = @_; + my $name = $hash->{NAME}; + + #find rooms to leave + my %oldrooms; + if (defined($hash->{helper}{myMUCJIDs})) { + foreach my $oldroom (@{$hash->{helper}{myMUCJIDs}}) { + $oldrooms{$oldroom} = 1; + } + + } + $hash->{helper}{myMUCJIDs} = (); + + #format of line: room@server/nick:pass,room2@server/nick2:pass + my @rooms = split /,/, $MUCJID; + + foreach my $roompass (@rooms) { + my ($room,$pass) = split /:/,$roompass; + + #add room to array + push @{ $hash->{helper}{myMUCJIDs} }, $room; + + #remove from rooms to leave + if (exists($oldrooms{$room})) { + delete $oldrooms{$room}; + } + #create new presence object + my $presence = Net::Jabber::Presence->new; + $presence->SetTo($room); + my $muc = $presence->NewChild('http://jabber.org/protocol/muc'); + + if($pass) { + $muc->SetPassword($pass); + } + #remove history + my $hist = $muc->AddHistory(); + $hist->SetMaxChars(0); + + #join the room (or change the nick) + $hash->{JabberDevice}->Send($presence); + } + + #leave old rooms + foreach my $room (keys %oldrooms) { + $hash->{JabberDevice}->PresenceSend(to => $room, type => 'unavailable'); + } + +} + +########################################## +# Checking for waiting Messages from the Jabber Server sub Jabber_PollMessages($) { @@ -328,7 +472,9 @@ Jabber_PollMessages($) } InternalTimer(gettimeofday()+$attr{$name}{PollTimer}, "Jabber_PollMessages", $hash,0); } -################################### + +########################################## +# Checking the Connection to the Jabber Server sub Jabber_CheckConnection($) { my ($hash) = @_; @@ -356,8 +502,32 @@ sub Jabber_CheckConnection($) #but that causes the callbacks to not work anymore, so we unweaken it here by initializing the callbacks again :) $hash->{JabberDevice}->InitCallbacks(); + #For MUC we need to check if the history function is in the namespace, if not our libraries are old and we need to hack it in + #I found this option in NET::Jabber::Owl + if (!exists($Net::XMPP::Namespaces::NS{'__netjabber__:iq:muc:history'})) { + $hash->{JabberDevice}->AddNamespace(ns => '__netjabber__:iq:muc:history', + tag => 'history', + xpath => { + MaxChars => { path => '@maxchars' }, + MaxStanzas => { path => '@maxstanzas' }, + Seconds => { path => '@seconds' }, + Since => { path => '@since' } + }, + docs => { + module => 'Net::Jabber', + }, + ); + #patch the already existing muc namespace to support the history function so we can call "AddHistory" later... + $Net::XMPP::Namespaces::NS{'http://jabber.org/protocol/muc'}->{xpath}->{History} = { + type => 'child', + path => 'history', + child => { ns => '__netjabber__:iq:muc:history' }, + calls => ['Add', 'Get', 'Set', 'Defined' ], + }; + } + #Needed for Message handling: - $hash->{JabberDevice}->SetMessageCallBacks(normal => sub { \&Jabber_INC_Message($hash,@_) }, chat => sub { \&Jabber_INC_Message($hash,@_) } ); + $hash->{JabberDevice}->SetMessageCallBacks(normal => sub { \&Jabber_INC_Message($hash,@_) }, chat => sub { \&Jabber_INC_Message($hash,@_) }, groupchat => sub { \&Jabber_INC_MUCMessage($hash,@_) } ); #Needed if someone wants to subscribe to us and is on the WhiteList $hash->{JabberDevice}->SetPresenceCallBacks( subscribe => sub { \&Jabber_INC_Subscribe($hash,@_) }, @@ -368,7 +538,9 @@ sub Jabber_CheckConnection($) unsubscribed => sub { \&Jabber_INC_Subscribe($hash,@_) }, error => sub { \&Jabber_INC_Subscribe($hash,@_) } ); - + if(exists($attr{$name}{OTREnable}) && $attr{$name}{OTREnable} == 1) { + Jabber_OTR_Init($hash); + } } if (!$hash->{JabberDevice}->Connected()) { @@ -404,6 +576,11 @@ sub Jabber_CheckConnection($) $hash->{STATE} = "Connected"; $hash->{CONNINFO} = "Connected to $hash->{helper}{server} with username $hash->{helper}{username}"; $hash->{JabberDevice}->RosterRequest(); + #join MUCs + if (defined($attr{$name}{MucJoin})) { + Jabber_MUCs_Join($hash,$attr{$name}{MucJoin}) if $attr{$name}{MucJoin} ne ""; + } + #Send Presence type only if we do not want to be availible if ($attr{$name}{OnlineStatus} ne "available") { $hash->{JabberDevice}->PresenceSend(type=>$attr{$name}{OnlineStatus}); @@ -422,19 +599,28 @@ sub Jabber_CheckConnection($) } ########################################## +# Incoming Subscribe events sub Jabber_INC_Subscribe { my($hash,$session_id, $presence) = @_; my $name = $hash->{NAME}; - Log 0, "$hash->{NAME} INC_Subscribe: Recv Prsence from: " . $presence->GetFrom() . " Type: ". $presence->GetType() if $debug; + Log 0, "$hash->{NAME} INC_Subscribe: Recv presence from: " . $presence->GetFrom() . " Type: ". $presence->GetType() if $debug; my $sender = $presence->GetFrom(); my $JID = new Net::Jabber::JID($sender); my $senderShort = $JID->GetJID("base"); - + my $senderLong = $JID->GetJID("full"); + my $mucRegexMatch = 0; + #Check the Whitelist if the sender is allowed to send us. - if ($senderShort =~ m/$attr{$name}{RecvWhitelist}/) { - Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ matched" if $debug; + if (defined($attr{$name}{MucRecvWhitelist})) { + if ($senderLong =~ m/$attr{$name}{MucRecvWhitelist}/) { + $mucRegexMatch = 1; + } + } + if ($senderShort =~ m/$attr{$name}{RecvWhitelist}/ || $mucRegexMatch) { + Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ matched" if $debug && $senderShort =~ m/$attr{$name}{RecvWhitelist}/; + Log 0, "$hash->{NAME} Regex (MUC) m/$attr{$name}{MucRecvWhitelist}/ matched" if $debug && $mucRegexMatch && $senderLong =~ m/$attr{$name}{MucRecvWhitelist}/; if ($presence->GetType() eq "subscribe") { #respond with authorization so they can see our online state @@ -445,11 +631,12 @@ sub Jabber_INC_Subscribe to=>$presence->GetFrom()); } } else { - Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ did not match" if $debug; + Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ and m/$attr{$name}{MucRecvWhitelist}/ did not match" if $debug; } - } + ########################################## +# Incoming Private (encrypted or plaintext) Message sub Jabber_INC_Message { my($hash,$session_id, $xmpp_message) = @_; my $name = $hash->{NAME}; @@ -457,43 +644,310 @@ sub Jabber_INC_Message { my $sender = $xmpp_message->GetFrom(); my $message = $xmpp_message->GetBody(); utf8::encode($message); - Log 0, "$hash->{NAME} INC_Message: $sender: $message\n" if $debug; + Log 0, "$hash->{NAME} INC_Message: $sender: $message" if $debug; my $JID = new Net::Jabber::JID($sender); my $senderShort = $JID->GetJID("base"); - #Check the Whitelist if the sender is allowed to send us. + #Check the Whitelist if the sender is allowed to send us, but not the "shortname" as this will strip the sendernickname off if ($senderShort =~ m/$attr{$name}{RecvWhitelist}/) { Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ matched" if $debug; - readingsBeginUpdate($hash); + # check to see if this is a OTR Message, if OTR is enabled + my $otr_message_recv = 0; + if ($hash->{helper}{otractive}) { + if ($message =~ m/^\?OTR/) { + #try to decrypt it + my $discard_msg = 0; + ($message, $discard_msg) = $hash->{OTR}->decrypt($JID->GetJID("full"), $message); + if (!$discard_msg && $message ne "") { + Log 0, "$hash->{NAME} INC_Message [OTR]: $sender: $message" if $debug; + utf8::encode($message); + $otr_message_recv = 1; + } elsif(!$discard_msg && $message eq "") { + Log 0, "$hash->{NAME} INC_Message [OTR]: We received an encrypted message from $senderShort but were unable to decrypt it (maybe also a control message)" if $debug; + } + } + } + #When we have got no message after the OTR decrypt, we can leave this function. + + if (!defined($message) || $message eq "") { + Log 0, "$hash->{NAME} Message is empty after OTR decrypt."; + return undef; + } + # Some IM clients send HTML, we need # to convert it to plain text # remove tags $message =~ s/<(.|\n)+?>//g; # convert "'s - $message =~ s/"/\"/g; - - - readingsBulkUpdate($hash,"Message","$sender: $message"); - readingsBulkUpdate($hash,"LastSenderJID","$sender"); - readingsBulkUpdate($hash,"LastMessage","$message"); - - my $response = "\n"; - my $checkfordel = substr($message, 0, 3); - Log 0, "$hash->{NAME} ReadingsUpdate: $message\n" if $debug; - - - #$response = $message; - #if (($response eq "\n") || $response eq "") { - #$response = $response."Nothing to display for command todo $message." - #} - - # $hash->{JabberDevice}->MessageSend(to=>$sender,body=>$response,type=>'chat'); - readingsEndUpdate($hash, 1); + $message =~ s/"/\\"/g; + # trim whitespaces at beginning and end + $message =~ s/^[\n\r\s]+|[\n\r\s]+$//g; + + #now, if the message is empty, (or was only filled with xml tags for various status infos), drop it + if ($message ne "") { + + readingsBeginUpdate($hash); + if ($otr_message_recv) { + readingsBulkUpdate($hash,"OTRMessage","$sender: $message"); + readingsBulkUpdate($hash,"OTRLastSenderJID","$sender"); + readingsBulkUpdate($hash,"OTRLastMessage","$message"); + Log 0, "$hash->{NAME} ReadingsUpdate [OTR]: $message" if $debug; + } else { + readingsBulkUpdate($hash,"Message","$sender: $message"); + readingsBulkUpdate($hash,"LastSenderJID","$sender"); + readingsBulkUpdate($hash,"LastMessage","$message"); + Log 0, "$hash->{NAME} ReadingsUpdate: $message" if $debug; + } + + readingsEndUpdate($hash, 1); + } else { + Log 0, "$hash->{NAME} Message was empty or full of xml tags - no readings update" if $debug; + } } else { Log 0, "$hash->{NAME} Regex m/$attr{$name}{RecvWhitelist}/ did not match" if $debug; } } + +########################################## +# Incoming MUC (Multi-User-Channel) Message +sub Jabber_INC_MUCMessage { + my($hash,$session_id, $xmpp_message) = @_; + + my $name = $hash->{NAME}; + + my $sender = $xmpp_message->GetFrom(); + my $message = $xmpp_message->GetBody(); + utf8::encode($message); + Log 0, "$hash->{NAME} INC_MUCMessage: $sender: $message\n" if $debug; + my $JID = new Net::Jabber::JID($sender); + my $senderShort = $JID->GetJID("base"); + my $senderLong = $JID->GetJID("full"); + + #filter MUC messages (ie subject set on join) + #filter own messages to prevent loop + foreach my $muc (@{$hash->{helper}{myMUCJIDs}}) { + my $mucJID = new Net::Jabber::JID($muc); + if ($JID->GetJID("full") eq $mucJID->GetJID("base")) { + #room send something to us + Log 0, "$hash->{NAME} ignoring message from room $senderShort" if $debug; + return undef; + } elsif ($JID->GetJID("full") eq $mucJID->GetJID("full")) { + #we received our own message + Log 0, "$hash->{NAME} ignoring message from ourself" if $debug; + return undef; + } + } + + #Check the Whitelist if the sender is allowed to send us, but not the "shortname" as this will strip the sendernickname off + if ($senderLong =~ m/$attr{$name}{MucRecvWhitelist}/) { + Log 0, "$hash->{NAME} Regex (MUC) m/$attr{$name}{MucRecvWhitelist}/ matched" if $debug; + + # Some IM clients send HTML, we need + # to convert it to plain text + # remove tags + $message =~ s/<(.|\n)+?>//g; + # convert "'s + $message =~ s/"/\\"/g; + # trim whitespaces at beginning and end + $message =~ s/^[\n\r\s]+|[\n\r\s]+$//g; + + #now, if the message is empty, (or was only filled with xml tags for various status infos), drop it + if ($message ne "") { + readingsBeginUpdate($hash); + + readingsBulkUpdate($hash,"MucMessage","$sender: $message"); + readingsBulkUpdate($hash,"MucLastSenderJID","$sender"); + readingsBulkUpdate($hash,"MucLastMessage","$message"); + Log 0, "$hash->{NAME} ReadingsUpdate: $message" if $debug; + + readingsEndUpdate($hash, 1); + } else { + Log 0, "$hash->{NAME} Message was empty or full of xml tags - no readings update" if $debug; + } + } else { + Log 0, "$hash->{NAME} Regex (MUC) m/$attr{$name}{MucRecvWhitelist}/ did not match" if $debug; + } +} + +########################################## +# OTR Specific functions +########################################## +sub +Jabber_OTR_Init($) +{ + my ($hash) = @_; + #check if Crypt::OTR is installed + if(!eval("require Crypt::OTR;")) { + $hash->{helper}{otractive} = 0; + $hash->{STATE} = $hash->{STATE}. " (OTR Error)"; + $hash->{OTR_STATE} = "Missing perl Module Crypt::OTR, OTR disabled."; +# $hash->{CONNINFO} = "Missing perl Module Crypt::OTR, OTR disabled."; + return undef; + } + + #find if the current directory is writeable to store our key, if not we will use /tmp as this is the most availible one. + my $otrCfgDir = AttrVal('global','modpath','.')."/log"; + $otrCfgDir = "/tmp" if ! -e $otrCfgDir || ! -w $otrCfgDir; + + Crypt::OTR->init; + my $otr_accname = $hash->{NAME}; + my $otr_proto = "FHEMJabber"; + my $otr = new Crypt::OTR( + account_name => $otr_accname, + protocol => $otr_proto, + config_dir => $otrCfgDir, + ); + + $hash->{OTR} = $otr; + $hash->{OTR}->set_callback('inject' => sub { \&Jabber_OTR_inject($hash,@_) }); + $hash->{OTR}->set_callback('otr_message' => sub { \&Jabber_OTR_system_message($hash,@_) }); + $hash->{OTR}->set_callback('verified' => sub { \&Jabber_OTR_connected_verified($hash,@_) }); + $hash->{OTR}->set_callback('unverified' => sub { \&Jabber_OTR_connected_unverified($hash,@_) }); + $hash->{OTR}->set_callback('disconnect' => sub { \&Jabber_OTR_disconnected($hash,@_) }); + $hash->{OTR}->set_callback('smp_request' => sub { \&Jabber_OTR_smprequest($hash,@_) }); + + + if (! -e $otrCfgDir."/otr.private_key-".lc($otr_accname)."-".lc($otr_proto)) { + #say we are genning Private key, and log the info, then execute it in another fork of fhem to prevent a 2h block + Log 0, "$hash->{NAME} Generating OTR private key, be prepared that this will take 2 hours or more. Check OTR_STATE (this is a one-time task)"; + $hash->{OTR_STATE} = "Generating OTR private key..."; + if(!exists($hash->{helper}{OTR_GENKEY_PID})) { + $hash->{helper}{OTR_GENKEY_PID} = BlockingCall("Jabber_OTR_GenPrivateKey", $hash->{NAME}."|".$otr_accname."|".$otr_proto."|".$otrCfgDir, "Jabber_OTR_GenPrivateKeyComplete"); + } else { + $hash->{OTR_STATE} = "Still generating OTR private key. Please be patient!"; + } + } else { + if(!exists($hash->{helper}{OTR_GENKEY_PID})) { + #Private key is there, everything is fine. + Log 0, "$hash->{NAME} OTR found privatekey, good!" if $debug; + $hash->{helper}{otractive} = 1; + $hash->{OTR_STATE} = "OTR enabled and active"; + Log 3, "$hash->{NAME} OTR successfully enabled and active" if $debug; + } else { + $hash->{OTR_STATE} = "Still generating OTR private key. Please be patient!"; + Log 3, "$hash->{NAME} OTR still generating OTR private key. Please be patient!"; + } + } +} +########################################## +# Generating private key in another process via Blocking.pm +sub Jabber_OTR_GenPrivateKey($) +{ + my ($callargs) = @_; + my ($hashname, $otr_accname,$otr_proto,$otrCfgDir) = split("\\|", $callargs); + my $otr = new Crypt::OTR( + account_name => $otr_accname, + protocol => $otr_proto, + config_dir => $otrCfgDir, + ); + + $otr->load_privkey(); + return "$hashname"; +} + +########################################## +# Completing generating private key +sub Jabber_OTR_GenPrivateKeyComplete($) +{ + my ($hashname) = @_; + return unless(defined($hashname)); + + my $hash = $defs{$hashname}; + $hash->{OTR_STATE} = "Finished generating OTR private key. OTR is now active."; + Log 0, "$hash->{NAME} Finished generating OTR private key"; + + delete($hash->{helper}{OTR_GENKEY_PID}); + + $hash->{helper}{otractive} = 1; + log 0, "$hash->{NAME} OTR successfully enabled and active" if $debug; +} + +########################################## +# called when OTR is ready to send a message after function calls (e.g. decrypt / smp / etc). +sub Jabber_OTR_inject { + my ($hash, $self, $account_name, $protocol, $dest_account, $message) = @_; + Log 0, "$hash->{NAME} [OTR Inject] Inject called: $message" if $debug; + Jabber_Set_Message($hash, $dest_account, $message); + #most times we await an answer, ignore the polltimer and check for new messages immediantly + Jabber_PollMessages($hash); +} + +########################################## +# called to display an OTR control message for a particular user or protocol +sub Jabber_OTR_system_message { + my ($hash, $self, $account_name, $protocol, $other_user, $otr_message) = @_; + Log 0, "$hash->{NAME} [OTR Sys MSG] $otr_message" if $debug; + return 1; +} + +########################################## +#called when an OTR Session has been established and has been verified by the SMP Protocol +sub Jabber_OTR_connected_verified { + my ($hash, $self, $from_account) = @_; + Log 0, "$hash->{NAME} [OTR] verified connection with $from_account established" if $debug; + $hash->{helper}{otrJIDs}{$from_account}{verified} = 1; + + #after this callback, OTR sends another message before the connection is fully established, so we have to delay that again for 5 secs before sending the actual message + my %h = (hash => $hash, from_account => $from_account); + InternalTimer(gettimeofday()+5, "Jabber_OTRDelaySend", \%h,0); +} + +########################################## +#called when an OTR Session has been established and is not verified +sub Jabber_OTR_connected_unverified { + my ($hash, $self, $from_account) = @_; + Log 0, "$hash->{NAME} [OTR] unverified connection with $from_account established" if $debug; + $hash->{helper}{otrJIDs}{$from_account}{verified} = 0; + + + #after this callback, OTR sends another message before the connection is fully established, so we have to delay that again for 5 secs before sending the actual message + my %h = (hash => $hash, from_account => $from_account); + InternalTimer(gettimeofday()+5, "Jabber_OTRDelaySend", \%h,0); +} + +########################################## +#called when the other end want to verify our identity +sub Jabber_OTR_smprequest { + my ($hash, $self, $account_name, $other_user) = @_; + + my $name = $hash->{NAME}; + Log 0, "$hash->{NAME} [OTR] User $other_user wants to verify our identity, sending shared secret response" if $debug; + if (defined($attr{$name}{OTRSharedSecret})) { + $hash->{OTR}->continue_smp($other_user, $attr{$name}{OTRSharedSecret}); + } else { + Log 0, "$hash->{NAME} [OTR] Error, no shared secret defined, sending bogus secret" if $debug; + $hash->{OTR}->continue_smp($other_user, "ImaBogusSecretBecauseNothingDefined"); + } +} + +########################################## +# Delayed sending message after established a secure connection +# This is needed because AFTER the function "un/verified" is called +# the system is still in the process of opening the encrypted communication +# +sub Jabber_OTRDelaySend($$) { + my $h = shift; + my $hash = $h->{hash}; + my $from_account = $h->{from_account}; + + #if we have unsent messages, send them now. + if (defined($hash->{helper}{otrJIDs}{$from_account}{waitingMsgs})) { + foreach my $waitingMsg (@{$hash->{helper}{otrJIDs}{$from_account}{waitingMsgs}}) { + Jabber_Set_OTRMessage($hash,$waitingMsg->{jid},$waitingMsg->{msg}); + } + } + +} + +########################################## +#called when an OTR Session has been disconnected +sub Jabber_OTR_disconnected { + my ($hash, $self, $from_account) = @_; + Log 0, "$hash->{NAME} [OTR] $from_account disconnected secure channel" if $debug; + delete ($hash->{helper}{otrJIDs}{$from_account}); +} + 1; @@ -508,7 +962,7 @@ sub Jabber_INC_Message { Jabber is another description for (XMPP) - a communications protocol for message-oriented middleware based on XML and - depending on the server - encrypt the communications channels.
For the user it is similar to other instant messaging Platforms like Facebook Chat, ICQ or Google's Hangouts - but free, Open Source and normally encrypted.
+ but free, Open Source and by default encrypted between the Jabber servers.

You need an account on a Jabber Server, you can find free services and more information on jabber.org
Discuss the module in the specific thread here.
@@ -522,6 +976,9 @@ sub Jabber_INC_Message {
  • Net::SSLeay

  • + Since version 1.5 it allows FHEM also to join MUC (Multi-User-Channels) and the use of OTR for end to end encryption
    + If you want to use OTR you must compile and install Crypt::OTR from CPAN on your own.
    +

    Define @@ -551,6 +1008,29 @@ sub Jabber_INC_Message {
    +
  • + set <name> msgmuc <channel> <msg> +
    + sends a message to the specified MUC group channel +
    + Examples: + +
  • +
    +
  • + set <name> msgotr <username> <msg> +
    + sends an Off-the-Record encrypted message to the specified username, if no OTR session is currently established it is being tried to esablish an OTR session with the specified user.
    + If the user does not have OTR support the message is discarded. +
    + Examples: + +
  • +
  • set <name> subscribe <username>
    @@ -568,22 +1048,30 @@ sub Jabber_INC_Message { Attributes

  • - -
  • PollTimer <seconds>
    - This is the interval in seconds at which the jabber server get polled.
    - Every interval the client checks if there are messages waiting and checks the connection to the server.
    - Don't set it over 10 seconds, as the client could get disconnected.
    + +
  • MucJoin channel1@server.com/mynick[:password]
    + Allows you to join one or more MUC's (Multi-User-Channel) with a specific Nick and a optional Password
    +
    + Default: empty (no messages accepted)
    + Examples:
    + +

  • + +
  • MucRecvWhitelist <Regex>
    + Same as RecvWhitelist but for MUC: Only if the Regex match, the client accepts and interpret the message. Everything else will be discarded.
    +
    + Default: empty (no messages accepted)
    + Examples:
    + +

  • + +
  • OTREnable 1|0
    + Enabled the use of Crypt::OTR for end to end encryption between a device and FHEM
    + You must have Crypt::OTR installed and a private key is being generated the first time you enable this option
    + Key generation can take more than 2 hours on a quiet system but will not block FHEM instead it will inform you if it has been finished
    + Key generation is a one-time-task

    - Default: 2 -

  • + Default: empty (OTR disabled) +
    + +
  • OTRSharedSecret aSecretKeyiOnlyKnow@@*
    + Optional shared secret to allow the other end to start a trust verification against FHEM with this shared key.
    + If the user starts a trust verification process the fingerprint of the FHEM private key will be saved at the user's device and the connection is trusted.
    + This will allow to inform the user if the private key has changed (ex. in Man-in-the-Middle attacks)
    +
    + Default: empty, please define a shared secret on your own. +


  • - - Generated events: + + Generated Readings/Events:
    Author's Notes:
    + Seit Version 1.5 kann dieses Modul in Multi-User-Channel (sogenannte MUC) beitreten und Off-the-Record (OTR) Ende-zu-Ende Verschlüsselung benutzen.
    + Wenn du OTR benutzen möchtest musst du dir Crypt::OTR von CPAN selbst installieren.
    + OTR ist nochmal ein zusätzlicher Sicherheitsrelevater Punkt, da die Kommunikation wirklich von Endgerät zu FHEM verschlüsselt wird und man sich nicht auf die Jabber Server Transportverschlüsselung verlassen muss.
    +

    Define @@ -678,6 +1241,30 @@ sub Jabber_INC_Message {
    +
  • + set <name> msgmuc <channel> <msg> +
    + Sendet eine Nachricht "msg" an dieJabber-MUC-Gruppe "channel".
    + Dabei wird ein eventuell mitgegebener Nickname von "channel" entfernt, so kann man direkt das Reading LastMessageJID benutzen.
    +
    + Beispiel: + +
  • +
    +
  • + set <name> msgotr <username> <msg> +
    + Sendet eine OTR verschlüsselte Nachricht an den "username", wenn keine aktive OTR Sitzung aufgebaut ist, wird versucht eine aufzubauen.
    + Wenn der Empfänger OTR nicht versteht, wird die Nachricht verworfen, d.h. sie wird auf keinen Fall im Klartext übertragen. +
    + Beispiel: + +
  • +
  • set <name> subscribe <username>
    @@ -695,23 +1282,31 @@ sub Jabber_INC_Message { Attribute

  • - -
  • PollTimer <seconds>
    - Dies ist der Intervall in der überprüft wird ob neue Nachrichten zur Verarbeitung beim Jabber Server anstehen.
    - Ebenfalls wird hiermit die Verbindung zum Server überprüft (Timeouts, DSL Disconnects etc.).
    - Setze es nicht über 10 Sekunden, die Verbindung kann sonst die ganze Zeit getrennt werden, Sie wird zwar wieder aufgebaut, aber nach 10 Sekunden brechen die meisten Server die Verbindung automatisch ab.
    + +
  • MucJoin channel1@server.com/mynick[:passwort]
    + Tritt dem MUC mit dem spezifizierten Nickname und dem optionalem Passwort bei.
    +
    + Standard: nicht definiert
    + Beispiele:
    + +

  • + +
  • MucRecvWhitelist <Regex>
    + Selbe funktion wie RecvWhitelist, aber für Gruppenräume: Nur wenn die Regex zutrifft, wird die Nachricht verarbeitet. Alles andere wird ignoriert.
    +
    + Standard: nicht definiert (keine Nachricht wird akzeptiert)
    + Beispiele:
    + +

  • + +
  • OTREnable 1|0
    + Schaltet die Verschlüsselungsfunktionen von Crypt::OTR für sichere Ende-zu-Ende Kummunikation in FHEM an oder aus.
    + Es muss zwangsläufig dafür Crypt::OTR installiert sein.
    + Ein Privater Schlüssel wird bei Erstbenutzung generiert, das kann mehr als 2 Stunden dauern!
    + Dafür ist das eine einmalige Sache und FHEM wird dadurch nicht blockiert. Im Device sieht man im OTR_STATE wenn der Private Schlüssel fertig ist.
    + Erst danach ist OTR aktiv.

    - Standard: 2 -

  • + Default: nicht definiert (OTR deaktiviert) +
    + +
  • OTRSharedSecret aSecretKeyiOnlyKnow@@*
    + Optionales geheimes Passwort, dass man vom Endgerät an FHEM schicken kann um zu beweisen, dass es sich tatsächlich um FHEM handelt und nicht um einen + Hacker der sich (z.b. bei dem Internetprovider) zwischengeschaltet hat. + Normalerweise bekommt das Endgerät eine Warnung wenn sich an einer bereits verifizierten Verbindung etwas geändert hat.
    + Diese Warnung sollte man dann sehr ernst nehmen. +
    + Default: nicht definiert, setze hier dein geheimes Passwort. +

  • + +
    - - Generierte events: + + Generierte Readings/Events:
    @@ -748,6 +1400,24 @@ sub Jabber_INC_Message { } +
  • Auf MUC Nachrichten lässt sich folgend reagieren, Augenmerk darauf legen dass der Nickname aus $lastsender in der msgmuc Funktion entfernt wird, damit die Nachricht an den Raum geht
    +
    define Jabber_Notify notify JabberClient1:MucMessage.* {
    +  my $lastsender=ReadingsVal("JabberClient1","LastSenderJID","0");
    +  my $lastmsg=ReadingsVal("JabberClient1","LastMessage","0");
    +  my $temperature=ReadingsVal("BU_Temperatur","temperature","0");
    +  fhem("set JabberClient1 msgmuc ". $lastsender . " Temp: ".$temperature);
    +}
    +        
    +
  • +
  • Auf OTR Nachrichten wird reagiert, wie auf normale private Nachrichten auch, jedoch wird mit der msgotr Funktion geantwortet:
    +
    define Jabber_Notify notify JabberClient1:OTRMessage.* {
    +  my $lastsender=ReadingsVal("JabberClient1","LastSenderJID","0");
    +  my $lastmsg=ReadingsVal("JabberClient1","LastMessage","0");
    +  my $temperature=ReadingsVal("BU_Temperatur","temperature","0");
    +  fhem("set JabberClient1 msgotr ". $lastsender . " Temp: ".$temperature);
    +}
    +        
    +
  • =end html_DE