2019-08-23 17:53:53 +00:00
|
|
|
|
--[[--
|
|
|
|
|
WPA client helper for Kobo.
|
|
|
|
|
]]
|
|
|
|
|
|
2020-09-15 18:39:32 +00:00
|
|
|
|
local FFIUtil = require("ffi/util")
|
2016-06-26 00:53:08 +00:00
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
2021-10-31 22:52:08 +00:00
|
|
|
|
local WpaClient = require("lj-wpaclient/wpaclient")
|
2017-04-29 14:30:16 +00:00
|
|
|
|
local UIManager = require("ui/uimanager")
|
2016-06-26 00:53:08 +00:00
|
|
|
|
local _ = require("gettext")
|
2020-09-15 18:39:32 +00:00
|
|
|
|
local T = FFIUtil.template
|
2016-06-26 00:53:08 +00:00
|
|
|
|
|
2016-10-18 05:02:06 +00:00
|
|
|
|
local CLIENT_INIT_ERR_MSG = _("Failed to initialize network control client: %1.")
|
|
|
|
|
|
2016-06-26 00:53:08 +00:00
|
|
|
|
local WpaSupplicant = {}
|
|
|
|
|
|
2019-08-23 17:53:53 +00:00
|
|
|
|
--- Gets network list.
|
2016-06-26 00:53:08 +00:00
|
|
|
|
function WpaSupplicant:getNetworkList()
|
2016-10-18 05:02:06 +00:00
|
|
|
|
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
|
|
|
|
|
if wcli == nil then
|
|
|
|
|
return nil, T(CLIENT_INIT_ERR_MSG, err)
|
|
|
|
|
end
|
|
|
|
|
|
2021-10-31 22:52:08 +00:00
|
|
|
|
local list
|
|
|
|
|
list, err = wcli:scanThenGetResults()
|
2016-06-26 00:53:08 +00:00
|
|
|
|
wcli:close()
|
2021-10-31 22:52:08 +00:00
|
|
|
|
if list == nil then
|
|
|
|
|
return nil, T("An error occurred while scanning: %1.", err)
|
|
|
|
|
end
|
2016-06-26 00:53:08 +00:00
|
|
|
|
|
|
|
|
|
local saved_networks = self:getAllSavedNetworks()
|
|
|
|
|
local curr_network = self:getCurrentNetwork()
|
|
|
|
|
|
2021-10-31 22:52:08 +00:00
|
|
|
|
for _, network in ipairs(list) do
|
2016-06-26 00:53:08 +00:00
|
|
|
|
network.signal_quality = network:getSignalQuality()
|
|
|
|
|
local saved_nw = saved_networks:readSetting(network.ssid)
|
2016-10-22 04:11:11 +00:00
|
|
|
|
if saved_nw then
|
2019-08-23 17:53:53 +00:00
|
|
|
|
--- @todo verify saved_nw.flags == network.flags? This will break if user changed the
|
2016-10-22 04:11:11 +00:00
|
|
|
|
-- network setting from [WPA-PSK-TKIP+CCMP][WPS][ESS] to [WPA-PSK-TKIP+CCMP][ESS]
|
2016-06-26 00:53:08 +00:00
|
|
|
|
network.password = saved_nw.password
|
2017-02-27 04:51:04 +00:00
|
|
|
|
network.psk = saved_nw.psk
|
2016-06-26 00:53:08 +00:00
|
|
|
|
end
|
2019-08-23 17:53:53 +00:00
|
|
|
|
--- @todo also verify bssid if it is not set to any
|
2016-06-26 00:53:08 +00:00
|
|
|
|
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
|
|
|
|
|
|
2017-02-27 04:51:04 +00:00
|
|
|
|
local function calculatePsk(ssid, pwd)
|
2019-08-23 17:53:53 +00:00
|
|
|
|
--- @todo calculate PSK with native function instead of shelling out
|
2017-02-27 04:51:04 +00:00
|
|
|
|
-- hostap's reference implementation is available at:
|
|
|
|
|
-- * /wpa_supplicant/wpa_passphrase.c
|
|
|
|
|
-- * /src/crypto/sha1-pbkdf2.c
|
2019-08-23 17:53:53 +00:00
|
|
|
|
-- see: <http://docs.ros.org/diamondback/api/wpa_supplicant/html/sha1-pbkdf2_8c_source.html>
|
2017-05-09 07:29:21 +00:00
|
|
|
|
local fp = io.popen(("wpa_passphrase %q %q"):format(ssid, pwd))
|
2017-02-27 04:51:04 +00:00
|
|
|
|
local out = fp:read("*a")
|
|
|
|
|
fp:close()
|
2021-09-08 22:34:26 +00:00
|
|
|
|
return string.match(out, "psk=([a-f0-9]+)")
|
2017-02-27 04:51:04 +00:00
|
|
|
|
end
|
|
|
|
|
|
2019-08-23 17:53:53 +00:00
|
|
|
|
--- Authenticates network.
|
2016-06-26 00:53:08 +00:00
|
|
|
|
function WpaSupplicant:authenticateNetwork(network)
|
2021-10-31 22:52:08 +00:00
|
|
|
|
local wcli, reply, err
|
2021-09-09 22:23:41 +00:00
|
|
|
|
|
2016-06-26 00:53:08 +00:00
|
|
|
|
wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
|
|
|
|
|
if not wcli then
|
2016-10-18 05:02:06 +00:00
|
|
|
|
return false, T(CLIENT_INIT_ERR_MSG, err)
|
2016-06-26 00:53:08 +00:00
|
|
|
|
end
|
|
|
|
|
|
2021-10-31 22:52:08 +00:00
|
|
|
|
reply, err = wcli:addNetwork()
|
|
|
|
|
if reply == nil then
|
|
|
|
|
return false, err
|
|
|
|
|
end
|
|
|
|
|
local nw_id = reply
|
2016-06-26 00:53:08 +00:00
|
|
|
|
|
2021-10-31 22:52:08 +00:00
|
|
|
|
reply, err = wcli:setNetwork(nw_id, "ssid", string.format("\"%s\"", network.ssid))
|
|
|
|
|
if reply == nil or reply == "FAIL" then
|
2017-02-20 08:42:23 +00:00
|
|
|
|
wcli:removeNetwork(nw_id)
|
2021-10-31 22:52:08 +00:00
|
|
|
|
return false, T("An error occurred while selecting network: %1.", err)
|
2017-02-20 08:42:23 +00:00
|
|
|
|
end
|
2021-09-08 20:48:58 +00:00
|
|
|
|
-- if password is empty it’s an open AP
|
2021-09-09 22:23:41 +00:00
|
|
|
|
if network.password and #network.password == 0 then -- Open AP
|
2021-10-31 22:52:08 +00:00
|
|
|
|
reply, err = wcli:setNetwork(nw_id, "key_mgmt", "NONE")
|
|
|
|
|
if reply == nil or reply == "FAIL" then
|
2021-09-08 20:48:58 +00:00
|
|
|
|
wcli:removeNetwork(nw_id)
|
2021-10-31 22:52:08 +00:00
|
|
|
|
return false, T("An error occurred while setting passwordless mode: %1.", err)
|
2021-09-08 20:48:58 +00:00
|
|
|
|
end
|
|
|
|
|
-- else it’s a WPA AP
|
|
|
|
|
else
|
|
|
|
|
if not network.psk then
|
|
|
|
|
network.psk = calculatePsk(network.ssid, network.password)
|
|
|
|
|
self:saveNetwork(network)
|
|
|
|
|
end
|
2021-10-31 22:52:08 +00:00
|
|
|
|
reply, err = wcli:setNetwork(nw_id, "psk", network.psk)
|
|
|
|
|
if reply == nil or reply == "FAIL" then
|
2021-09-08 20:48:58 +00:00
|
|
|
|
wcli:removeNetwork(nw_id)
|
2021-10-31 22:52:08 +00:00
|
|
|
|
return false, T("An error occurred while setting password: %1.", err)
|
2021-09-08 20:48:58 +00:00
|
|
|
|
end
|
2017-02-20 08:42:23 +00:00
|
|
|
|
end
|
2016-06-26 00:53:08 +00:00
|
|
|
|
wcli:enableNetworkByID(nw_id)
|
|
|
|
|
|
|
|
|
|
wcli:attach()
|
|
|
|
|
local cnt = 0
|
|
|
|
|
local failure_cnt = 0
|
|
|
|
|
local max_retry = 30
|
|
|
|
|
local info = InfoMessage:new{text = _("Authenticating…")}
|
2021-10-31 22:52:08 +00:00
|
|
|
|
local success = false
|
|
|
|
|
local msg = _("Authenticated")
|
2016-06-26 00:53:08 +00:00
|
|
|
|
UIManager:show(info)
|
|
|
|
|
UIManager:forceRePaint()
|
|
|
|
|
while cnt < max_retry do
|
2021-10-31 22:52:08 +00:00
|
|
|
|
-- 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...
|
2016-06-26 00:53:08 +00:00
|
|
|
|
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
|
2021-10-31 22:52:08 +00:00
|
|
|
|
success = true
|
2016-06-26 00:53:08 +00:00
|
|
|
|
break
|
|
|
|
|
elseif ev:isAuthFailed() then
|
|
|
|
|
failure_cnt = failure_cnt + 1
|
|
|
|
|
if failure_cnt > 3 then
|
2021-10-31 22:52:08 +00:00
|
|
|
|
success, msg = false, _("Failed to authenticate")
|
2016-06-26 00:53:08 +00:00
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
else
|
2021-10-31 22:52:08 +00:00
|
|
|
|
wcli:waitForEvent(1 * 1000)
|
2016-06-26 00:53:08 +00:00
|
|
|
|
cnt = cnt + 1
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-10-31 22:52:08 +00:00
|
|
|
|
if success ~= true then
|
|
|
|
|
wcli:removeNetwork(nw_id)
|
|
|
|
|
end
|
2016-06-26 00:53:08 +00:00
|
|
|
|
wcli:close()
|
|
|
|
|
UIManager:close(info)
|
|
|
|
|
UIManager:forceRePaint()
|
|
|
|
|
if cnt >= max_retry then
|
2021-10-31 22:52:08 +00:00
|
|
|
|
success, msg = false, _("Timed out")
|
2016-06-26 00:53:08 +00:00
|
|
|
|
end
|
2021-10-31 22:52:08 +00:00
|
|
|
|
return success, msg
|
2016-06-26 00:53:08 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function WpaSupplicant:disconnectNetwork(network)
|
|
|
|
|
if not network.wpa_supplicant_id then return end
|
2016-10-18 05:02:06 +00:00
|
|
|
|
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
|
|
|
|
|
if wcli == nil then
|
|
|
|
|
return nil, T(CLIENT_INIT_ERR_MSG, err)
|
|
|
|
|
end
|
2016-06-26 00:53:08 +00:00
|
|
|
|
wcli:removeNetwork(network.wpa_supplicant_id)
|
|
|
|
|
wcli:close()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function WpaSupplicant:getCurrentNetwork()
|
2016-10-18 05:02:06 +00:00
|
|
|
|
local wcli, err = WpaClient.new(self.wpa_supplicant.ctrl_interface)
|
|
|
|
|
if wcli == nil then
|
|
|
|
|
return nil, T(CLIENT_INIT_ERR_MSG, err)
|
|
|
|
|
end
|
2016-06-26 00:53:08 +00:00
|
|
|
|
local nw = wcli:getCurrentNetwork()
|
|
|
|
|
wcli:close()
|
|
|
|
|
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
|