From f580b7f0e7c66429fcaca78a966916f54e91defe Mon Sep 17 00:00:00 2001 From: wuehler <> Date: Sun, 11 Mar 2018 14:54:39 +0000 Subject: [PATCH] 74_Unifi: added set updateClient, encrypt user and password git-svn-id: https://svn.fhem.de/fhem/trunk@16383 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/74_Unifi.pm | 191 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 178 insertions(+), 14 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index adf9898c5..9b2b16818 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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: 74_Unifi: added set updateClient, encrypt user and password - change: 49_TBot_List: deleteonly lists / internal changes - change: 50_TelegramBot: more silent cmds, caption formatting,several fixes - change: lib/OWNet.pm: updated to latest version from diff --git a/fhem/FHEM/74_Unifi.pm b/fhem/FHEM/74_Unifi.pm index 116ab51a4..140d66205 100644 --- a/fhem/FHEM/74_Unifi.pm +++ b/fhem/FHEM/74_Unifi.pm @@ -16,6 +16,8 @@ # - change: 74_Unifi: SSIDs-Readings and drop-downs use goodReadingName() # V 2.1.4 # - feature: 74_Unifi: added voucher-functions +# V 2.2 +# - feature: 74_Unifi: added set updateClien, encrypt user and password package main; @@ -69,6 +71,8 @@ sub Unifi_BlockClient_Send($@); sub Unifi_BlockClient_Receive($); sub Unifi_UnblockClient_Send($@); sub Unifi_UnblockClient_Receive($); +sub Unifi_UpdateClient_Send($$); +sub Unifi_UpdateClient_Receive($); sub Unifi_SwitchSiteLEDs_Send($$); sub Unifi_SwitchSiteLEDs_Receive($); sub Unifi_WlanconfRest_Send($$@); @@ -83,6 +87,8 @@ sub Unifi_getNextVoucherForNote($$); sub Unifi_NextUpdateFn($$); sub Unifi_ReceiveFailure($$); sub Unifi_CONNECTED($@); +sub Unifi_encrypt($); +sub Unifi_encrypt($); sub Unifi_Whoami(); sub Unifi_Whowasi(); ##############################################################################} @@ -138,13 +144,23 @@ sub Unifi_Define($$) { loglevel => AttrVal($name,"httpLoglevel",5), sslargs => { SSL_verify_mode => 0 }, }; - if($hash->{unifi}->{version} == 3) { - ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/; - $hash->{httpParams}->{loginData} = "login=login&username=".$a[4]."&password=".$a[5]; - }else { - ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/; - $hash->{httpParams}->{loginData} = '{"username":"'.$a[4].'", "password":"'.$a[5].'"}'; - } + #if($hash->{unifi}->{version} == 3) { + # ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/; + # $hash->{httpParams}->{loginData} = "login=login&username=".$a[4]."&password=".$a[5]; + #}else { + # ( $hash->{httpParams}->{loginUrl} = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/; + # $hash->{httpParams}->{loginData} = '{"username":"'.$a[4].'", "password":"'.$a[5].'"}'; + #} + + my $username = Unifi_encrypt($a[4]); + my $password = Unifi_encrypt($a[5]); + $hash->{helper}{username} = $username; + $hash->{helper}{password} = $password; + my $define="$a[2] $a[3] $username $password"; + $define.=" $a[6]" if($a[6]); + $define.=" $a[7]" if($a[7]); + $define.=" $a[8]" if($a[8]); + $hash->{DEF} = $define; Log3 $name, 5, "$name: Defined with url:$hash->{unifi}->{url}, interval:$hash->{unifi}->{interval}, version:$hash->{unifi}->{version}"; return undef; @@ -197,14 +213,14 @@ sub Unifi_Set($@) { my $apNames = Unifi_ApNames($hash); my $SSIDs = Unifi_SSIDs($hash); - if($setName !~ /archiveAlerts|restartAP|setLocateAP|unsetLocateAP|disconnectClient|update|clear|poeMode|blockClient|unblockClient|enableWLAN|disableWLAN|switchSiteLEDs|createVoucher/) { + if($setName !~ /archiveAlerts|restartAP|setLocateAP|unsetLocateAP|disconnectClient|update|updateClient|clear|poeMode|blockClient|unblockClient|enableWLAN|disableWLAN|switchSiteLEDs|createVoucher/) { return "Unknown argument $setName, choose one of update:noArg " ."clear:all,readings,clientData,allData,voucherCache " .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "archiveAlerts:noArg " : "") .(($apNames && Unifi_CONNECTED($hash)) ? "restartAP:all,$apNames setLocateAP:all,$apNames unsetLocateAP:all,$apNames " : "") .(($clientNames && Unifi_CONNECTED($hash)) ? "disconnectClient:all,$clientNames " : "") ."poeMode createVoucher enableWLAN:$SSIDs disableWLAN:$SSIDs " - ."blockClient:$clientNames unblockClient:$clientNames switchSiteLEDs:on,off"; + ."blockClient:$clientNames unblockClient:$clientNames switchSiteLEDs:on,off updateClient"; } else { Log3 $name, 4, "$name: set $setName"; @@ -284,6 +300,10 @@ sub Unifi_Set($@) { return "$hash->{NAME}: Unknown SSID '$setVal' in command '$setName', choose one of: all,$SSIDs"; } } + elsif ($setName eq 'updateClient') { + return "enter mac of client" if( ! defined $setVal); + Unifi_UpdateClient_Send($hash,$setVal); + } elsif ($setName eq 'poeMode') { return "usage: $setName " if( !$setVal3 ); my $apRef; @@ -471,12 +491,12 @@ sub Unifi_Get($@) { my $clientNames = Unifi_ClientNames($hash); - if($getName !~ /events|clientData|unarchivedAlerts|poeState|voucherList|voucher/) { + if($getName !~ /events|clientData|unarchivedAlerts|poeState|voucherList|voucher|showAccount/) { return "Unknown argument $getName, choose one of " .((defined $hash->{events}[0] && scalar @{$hash->{events}}) ? "events:noArg " : "") .((defined $hash->{alerts_unarchived}[0] && scalar @{$hash->{alerts_unarchived}}) ? "unarchivedAlerts:noArg " : "") .(($clientNames) ? "clientData:all,$clientNames " : "") - ."poeState voucherList:all,$voucherNotes voucher:$voucherNotes"; + ."poeState voucherList:all,$voucherNotes voucher:$voucherNotes showAccount"; } elsif ($getName eq 'poeState') { my $poeState; @@ -597,6 +617,18 @@ sub Unifi_Get($@) { } return $returnedVoucherCode; } + elsif( $getName eq 'showAccount' ) { + my $user = $hash->{helper}{username}; + my $password = $hash->{helper}{password}; + + return 'no user set' if( !$user ); + return 'no password set' if( !$password ); + + $user = Unifi_decrypt( $user ); + $password = Unifi_decrypt( $password ); + + return "user: $user\npassword: $password"; + } return undef; } ############################################################################### @@ -711,11 +743,23 @@ sub Unifi_Login_Send($) { my ($hash) = @_; my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); Log3 $name, 5, "$name ($self) - executed."; - + + my ($loginurl,$logindata); + my $user = $hash->{helper}{username}; + my $password = $hash->{helper}{password}; + $user = Unifi_decrypt( $user ); + $password = Unifi_decrypt( $password ); + if($hash->{unifi}->{version} == 3) { + ( $loginurl = $hash->{unifi}->{url} ) =~ s/api\/s.+/login/; + $logindata = "login=login&username=".$user."&password=".$password; + }else { + ( $loginurl = $hash->{unifi}->{url} ) =~ s/api\/s.+/api\/login/; + $logindata = '{"username":"'.$user.'", "password":"'.$password.'"}'; + } HttpUtils_NonblockingGet( { %{$hash->{httpParams}}, - url => $hash->{httpParams}->{loginUrl}, - data => $hash->{httpParams}->{loginData}, + url => $loginurl, + data => $logindata, callback => \&Unifi_Login_Receive } ); return undef; @@ -1300,6 +1344,83 @@ sub Unifi_DisconnectClient_Receive($) { return undef; } ############################################################################### + +sub Unifi_UpdateClient_Send($$) { + my ($hash,$mac) = @_; + my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); + Log3 $name, 5, "$name ($self) - executed with mac ".$mac; + + HttpUtils_NonblockingGet( { + %{$hash->{httpParams}}, + method => "GET", + url => $hash->{unifi}->{url}."stat/user/".$mac, + callback => \&Unifi_UpdateClient_Receive + } ); + return undef; +} +############################################################################### +sub Unifi_UpdateClient_Receive($) { + my ($param, $err, $data) = @_; + my ($name,$self,$hash) = ($param->{hash}->{NAME},Unifi_Whoami(),$param->{hash}); + Log3 $name, 5, "$name ($self) - executed."; + + if ($err ne "") { + Unifi_ReceiveFailure($hash,{rc => 'Error while requesting', msg => $param->{url}." - $err"}); + } + elsif ($data ne "") { + if ($param->{code} == 200 || $param->{code} == 400 || $param->{code} == 401) { + eval { $data = decode_json($data); 1; } or do { $data = { meta => {rc => 'error.decode_json', msg => $@} }; }; + + if ($data->{meta}->{rc} eq "ok") { + Log3 $name, 5, "$name ($self) - state:'$data->{meta}->{rc}'"; + + my $apNames = {}; + for my $apID (keys %{$hash->{accespoints}}) { + my $apRef = $hash->{accespoints}->{$apID}; + $apNames->{$apRef->{mac}} = $apRef->{name} ? $apRef->{name} : $apRef->{ip}; + } + + #$hash->{unifi}->{connectedClients} = undef; + for my $h (@{$data->{data}}) { + $hash->{unifi}->{connectedClients}->{$h->{user_id}} = 1; + $hash->{clients}->{$h->{user_id}} = $h; + + readingsBeginUpdate($hash); + if(defined $h->{user_id}){ + $hash->{unifi}->{connectedClients}->{$h->{user_id}} = $h; + $hash->{clients}->{$h->{user_id}} = $h; + my $clientRef = $hash->{clients}->{$h->{user_id}}; + my $clientName = Unifi_ClientNames($hash,$h->{user_id},'makeAlias'); + my $apName = "unknown"; + if ($clientRef->{is_wired} + && defined $clientRef->{sw_mac} && defined($apNames->{$clientRef->{sw_mac}}) ) { + $apName = $apNames->{$clientRef->{sw_mac}}; + } elsif (defined $clientRef->{ap_mac} && defined($apNames->{$clientRef->{ap_mac}}) ) { + $apName = $apNames->{$clientRef->{ap_mac}}; + } + + readingsBulkUpdate($hash,$clientName."_hostname",(defined $clientRef->{hostname}) ? $clientRef->{hostname} : (defined $clientRef->{ip}) ? $clientRef->{ip} : 'Unknown'); + readingsBulkUpdate($hash,$clientName."_last_seen",strftime "%Y-%m-%d %H:%M:%S",localtime($clientRef->{last_seen})); + readingsBulkUpdate($hash,$clientName."_uptime",$clientRef->{uptime}); + readingsBulkUpdate($hash,$clientName."_snr",$clientRef->{rssi}); + readingsBulkUpdate($hash,$clientName."_accesspoint",$apName); + readingsBulkUpdate($hash,$clientName,'connected'); + }else{ + Log3 $name, 5, "$name ($self) - Client ".$h->{hostname}." previously connected is now disconnected."; + readingsBulkUpdate($hash,$h->{hostname},'disconnected'); + } + readingsEndUpdate($hash,1); + } + #$hash->{clients}->{$data->{data}[0]->{user_id}} = $data->{data}; + } + else { Unifi_ReceiveFailure($hash,$data->{meta}); } + } else { + Unifi_ReceiveFailure($hash,{rc => $param->{code}, msg => "Failed with HTTP Code $param->{code}."}); + } + } + return undef; +} +############################################################################### sub Unifi_BlockClient_Send($@) { my ($hash,@clients) = @_; my ($name,$self) = ($hash->{NAME},Unifi_Whoami()); @@ -1947,6 +2068,43 @@ sub Unifi_CONNECTED($@) { } } } + +############################################################################### + +sub +Unifi_encrypt($) +{ + my ($decoded) = @_; + my $key = getUniqueId(); + my $encoded; + + return $decoded if( $decoded =~ /^crypt:(.*)/ ); + + for my $char (split //, $decoded) { + my $encode = chop($key); + $encoded .= sprintf("%.2x",ord($char)^ord($encode)); + $key = $encode.$key; + } + + return 'crypt:'. $encoded; +} +sub +Unifi_decrypt($) +{ + my ($encoded) = @_; + my $key = getUniqueId(); + my $decoded; + + $encoded = $1 if( $encoded =~ /^crypt:(.*)/ ); + + for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) { + my $decode = chop($key); + $decoded .= chr(ord($char)^ord($decode)); + $key = $decode.$key; + } + + return $decoded; +} ############################################################################### sub Unifi_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; } @@ -2044,6 +2202,9 @@ Or you can use the other readings or set and get features to control your unifi-
  • set <name> update
    Makes immediately a manual update.

  • +
  • set <name> updateClient <mac>
    + Makes immediately a manual update of the client specified by MAC-Adress.
  • +
  • set <name> clear <readings|clientData|voucherCache|all>
    Clears the readings, clientData, voucherCache or all.

  • @@ -2096,6 +2257,8 @@ Or you can use the other readings or set and get features to control your unifi- Show next voucher-code with specified note. If <note> is used in voucherCache the voucher will be marked as delivered
  • get <name> voucherList [all|note]
    Show list of vouchers (all or with specified note only).
  • +
  • get <name> showAccount
    + Show decrypted user and passwort.