2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/ui/network/wpa_supplicant.lua
Wim de With 17a4aa962f
Fix connection bug with non-ASCII SSIDs in wpa_supplicant (#11089)
* Bump base

includes:

koreader/koreader-base#1691
koreader/koreader-base#1692
koreader/koreader-base#1689
koreader/koreader-base#1690
koreader/koreader-base#1693

* Integrate decoding of SSIDs within wpa_supplicant

The UTF-8 decoding of SSIDs is specific to wpa_supplicant. In this
patch, we move all of this decoding logic to the wpa_supplicant module.
We expose the raw bytes of the SSID to the NetworkMgr code, and make
sure to always fix bad UTF-8 before we display the SSID to the user.

Within the wpa_supplicant module, we replace the call to the
wpa_passphrase binary to get the PSK with a direct function call to
OpenSSL. This allows us to calculate the PSK over any arbitrary bytes,
including UTF-8. In the same vein, we use the hex-encoded SSID to
communicate with wpa_supplicant when setting up the network to support
arbitrary bytes in the SSID.

Unfortunately, we also remove the tests, as there is no way to unit test
local functions.
2023-11-09 21:08:26 +01:00

215 lines
7.1 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[--
WPA client helper for Kobo.
]]
local crypto = require("ffi/crypto")
local bin_to_hex = require("ffi/sha2").bin_to_hex
local FFIUtil = require("ffi/util")
local InfoMessage = require("ui/widget/infomessage")
local WpaClient = require("lj-wpaclient/wpaclient")
local UIManager = require("ui/uimanager")
local _ = require("gettext")
local T = FFIUtil.template
local CLIENT_INIT_ERR_MSG = _("Failed to initialize network control client: %1.")
local WpaSupplicant = {}
local function decodeSSID(ssid)
local decode = function(b)
local c = string.char(tonumber(b, 16))
-- This is a hack that allows us to make sure that any decoded backslash
-- does not get replaced in the step that replaces double backslashes.
if c == "\\" then
return "\\\\"
else
return c
end
end
local decoded = ssid:gsub("%f[\\]\\x(%x%x)", decode)
decoded = decoded:gsub("\\\\", "\\")
return decoded
end
--- Gets network list.
function WpaSupplicant:getNetworkList()
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
if wcli == nil then
return nil, T(CLIENT_INIT_ERR_MSG, err)
end
local list
list, err = wcli:scanThenGetResults()
wcli:close()
if list == nil then
return nil, T("An error occurred while scanning: %1.", err)
end
local saved_networks = self:getAllSavedNetworks()
local curr_network = self:getCurrentNetwork()
for _, network in ipairs(list) do
network.ssid = decodeSSID(network.ssid)
network.signal_quality = network:getSignalQuality()
local saved_nw = saved_networks:readSetting(network.ssid)
if saved_nw then
--- @todo verify saved_nw.flags == network.flags? This will break if user changed the
-- network setting from [WPA-PSK-TKIP+CCMP][WPS][ESS] to [WPA-PSK-TKIP+CCMP][ESS]
network.password = saved_nw.password
network.psk = saved_nw.psk
end
--- @todo also verify bssid if it is not set to any
if curr_network and curr_network.ssid == network.ssid then
network.connected = true
network.wpa_supplicant_id = curr_network.id
end
end
return list
end
local function calculatePsk(ssid, pwd)
return bin_to_hex(crypto.pbkdf2_hmac_sha1(pwd, ssid, 4096, 32))
end
--- Authenticates network.
function WpaSupplicant:authenticateNetwork(network)
local wcli, reply, err
wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
if not wcli then
return false, T(CLIENT_INIT_ERR_MSG, err)
end
reply, err = wcli:addNetwork()
if reply == nil then
return false, err
end
local nw_id = reply
reply, err = wcli:setNetwork(nw_id, "ssid", bin_to_hex(network.ssid))
if reply == nil or reply == "FAIL" then
wcli:removeNetwork(nw_id)
return false, T("An error occurred while selecting network: %1.", err)
end
-- if password is empty its an open AP
if network.password and #network.password == 0 then -- Open AP
reply, err = wcli:setNetwork(nw_id, "key_mgmt", "NONE")
if reply == nil or reply == "FAIL" then
wcli:removeNetwork(nw_id)
return false, T("An error occurred while setting passwordless mode: %1.", err)
end
-- else its a WPA AP
else
if not network.psk then
network.psk = calculatePsk(network.ssid, network.password)
self:saveNetwork(network)
end
reply, err = wcli:setNetwork(nw_id, "psk", network.psk)
if reply == nil or reply == "FAIL" then
wcli:removeNetwork(nw_id)
return false, T("An error occurred while setting password: %1.", err)
end
end
wcli:enableNetworkByID(nw_id)
wcli:attach()
local cnt = 0
local failure_cnt = 0
local max_retry = 30
local info = InfoMessage:new{text = _("Authenticating…")}
local success = false
local msg = _("Authenticated")
UIManager:show(info)
UIManager:forceRePaint()
while cnt < max_retry do
-- Start by checking if we're not actually connected already...
-- NOTE: This is mainly to catch corner-cases where our preferred network list differs from the system's,
-- and ours happened to be sorted earlier because of a better signal quality...
local connected, state = wcli:getConnectedNetwork()
if connected then
network.wpa_supplicant_id = connected.id
network.ssid = connected.ssid
success = true
break
else
if state then
UIManager:close(info)
-- Make the state prettier
local first, rest = state:sub(1, 1), state:sub(2)
info = InfoMessage:new{text = string.upper(first) .. string.lower(rest) .. ""}
UIManager:show(info)
UIManager:forceRePaint()
end
end
-- Otherwise, poke at the wpa_supplicant socket for a bit...
local ev = wcli:readEvent()
if ev ~= nil then
if not ev:isScanEvent() then
UIManager:close(info)
info = InfoMessage:new{text = ev.msg}
UIManager:show(info)
UIManager:forceRePaint()
end
if ev:isAuthSuccessful() then
network.wpa_supplicant_id = nw_id
success = true
break
elseif ev:isAuthFailed() then
failure_cnt = failure_cnt + 1
if failure_cnt > 3 then
success, msg = false, _("Failed to authenticate")
break
end
end
else
wcli:waitForEvent(1 * 1000)
cnt = cnt + 1
end
end
if success ~= true then
wcli:removeNetwork(nw_id)
end
wcli:close()
UIManager:close(info)
UIManager:forceRePaint()
if cnt >= max_retry then
success, msg = false, _("Timed out")
end
return success, msg
end
function WpaSupplicant:disconnectNetwork(network)
if not network.wpa_supplicant_id then return end
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
if wcli == nil then
return nil, T(CLIENT_INIT_ERR_MSG, err)
end
wcli:removeNetwork(network.wpa_supplicant_id)
wcli:close()
end
function WpaSupplicant:getCurrentNetwork()
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
if wcli == nil then
return nil, T(CLIENT_INIT_ERR_MSG, err)
end
local nw = wcli:getCurrentNetwork()
wcli:close()
if nw ~= nil then
nw.ssid = decodeSSID(nw.ssid)
end
return nw
end
function WpaSupplicant.init(network_mgr, options)
network_mgr.wpa_supplicant = {ctrl_interface = options.ctrl_interface}
network_mgr.getNetworkList = WpaSupplicant.getNetworkList
network_mgr.getCurrentNetwork = WpaSupplicant.getCurrentNetwork
network_mgr.authenticateNetwork = WpaSupplicant.authenticateNetwork
network_mgr.disconnectNetwork = WpaSupplicant.disconnectNetwork
end
return WpaSupplicant