2020-01-04 00:18:51 +00:00
|
|
|
local BD = require("ui/bidi")
|
2018-05-13 19:27:52 +00:00
|
|
|
local DataStorage = require("datastorage")
|
|
|
|
local Device = require("device")
|
2021-05-31 19:18:04 +00:00
|
|
|
local Dispatcher = require("dispatcher")
|
2018-05-13 19:27:52 +00:00
|
|
|
local InfoMessage = require("ui/widget/infomessage") -- luacheck:ignore
|
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
2018-09-04 21:55:58 +00:00
|
|
|
local ffiutil = require("ffi/util")
|
2018-05-13 19:27:52 +00:00
|
|
|
local logger = require("logger")
|
|
|
|
local util = require("util")
|
|
|
|
local _ = require("gettext")
|
2018-09-04 21:55:58 +00:00
|
|
|
local T = ffiutil.template
|
2018-05-13 19:27:52 +00:00
|
|
|
|
2018-07-15 16:39:52 +00:00
|
|
|
-- This plugin uses a patched dropbear that adds two things:
|
|
|
|
-- the -n option to bypass password checks
|
|
|
|
-- reads the authorized_keys file from the relative path: settings/SSH/authorized_keys
|
2018-05-13 19:27:52 +00:00
|
|
|
|
|
|
|
local path = DataStorage:getFullDataDir()
|
2018-07-25 18:01:55 +00:00
|
|
|
if not util.pathExists("dropbear") then
|
2018-05-13 19:27:52 +00:00
|
|
|
return { disabled = true, }
|
|
|
|
end
|
|
|
|
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local SSH = WidgetContainer:extend{
|
2020-12-04 15:04:54 +00:00
|
|
|
name = "SSH",
|
2018-05-13 19:27:52 +00:00
|
|
|
is_doc_only = false,
|
|
|
|
}
|
|
|
|
|
|
|
|
function SSH:init()
|
|
|
|
self.SSH_port = G_reader_settings:readSetting("SSH_port") or "2222"
|
2021-05-31 19:18:04 +00:00
|
|
|
self.allow_no_password = G_reader_settings:isTrue("SSH_allow_no_password")
|
2018-05-13 19:27:52 +00:00
|
|
|
self.ui.menu:registerToMainMenu(self)
|
2021-05-31 19:18:04 +00:00
|
|
|
self:onDispatcherRegisterActions()
|
2018-05-13 19:27:52 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function SSH:start()
|
2018-07-16 15:24:04 +00:00
|
|
|
local cmd = string.format("%s %s %s %s%s %s",
|
2018-07-25 18:01:55 +00:00
|
|
|
"./dropbear",
|
2018-07-15 16:39:52 +00:00
|
|
|
"-E",
|
|
|
|
"-R",
|
2018-05-13 19:27:52 +00:00
|
|
|
"-p", self.SSH_port,
|
|
|
|
"-P /tmp/dropbear_koreader.pid")
|
|
|
|
if self.allow_no_password then
|
|
|
|
cmd = string.format("%s %s", cmd, "-n")
|
|
|
|
end
|
2018-07-15 16:39:52 +00:00
|
|
|
|
|
|
|
-- Make a hole in the Kindle's firewall
|
|
|
|
if Device:isKindle() then
|
2018-05-13 19:27:52 +00:00
|
|
|
os.execute(string.format("%s %s %s",
|
|
|
|
"iptables -A INPUT -p tcp --dport", self.SSH_port,
|
|
|
|
"-m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT"))
|
|
|
|
os.execute(string.format("%s %s %s",
|
|
|
|
"iptables -A OUTPUT -p tcp --sport", self.SSH_port,
|
|
|
|
"-m conntrack --ctstate ESTABLISHED -j ACCEPT"))
|
|
|
|
end
|
|
|
|
-- An SSH/telnet server of course needs to be able to manipulate pseudoterminals...
|
2018-07-15 16:39:52 +00:00
|
|
|
-- Kobo's init scripts fail to set this up...
|
|
|
|
if Device:isKobo() then
|
|
|
|
os.execute([[if [ ! -d "/dev/pts" ] ; then
|
|
|
|
mkdir -p /dev/pts
|
|
|
|
mount -t devpts devpts /dev/pts
|
|
|
|
fi]])
|
|
|
|
end
|
|
|
|
|
2018-05-13 19:27:52 +00:00
|
|
|
if not util.pathExists(path.."/settings/SSH/") then
|
|
|
|
os.execute("mkdir "..path.."/settings/SSH")
|
|
|
|
end
|
|
|
|
logger.dbg("[Network] Launching SSH server : ", cmd)
|
|
|
|
if os.execute(cmd) == 0 then
|
|
|
|
local info = InfoMessage:new{
|
2018-12-24 19:36:09 +00:00
|
|
|
timeout = 10,
|
2020-01-01 11:41:37 +00:00
|
|
|
-- @translators: %1 is the SSH port, %2 is the network info.
|
2021-05-31 19:18:04 +00:00
|
|
|
text = T(_("SSH server started.\n\nSSH port: %1\n%2"),
|
2020-01-01 11:41:37 +00:00
|
|
|
self.SSH_port,
|
2018-05-13 19:27:52 +00:00
|
|
|
Device.retrieveNetworkInfo and Device:retrieveNetworkInfo() or _("Could not retrieve network info.")),
|
|
|
|
}
|
|
|
|
UIManager:show(info)
|
|
|
|
else
|
|
|
|
local info = InfoMessage:new{
|
2021-05-31 19:18:04 +00:00
|
|
|
icon = "notice-warning",
|
|
|
|
text = _("Failed to start SSH server."),
|
2018-05-13 19:27:52 +00:00
|
|
|
}
|
|
|
|
UIManager:show(info)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function SSH:isRunning()
|
|
|
|
return util.pathExists("/tmp/dropbear_koreader.pid")
|
|
|
|
end
|
|
|
|
|
|
|
|
function SSH:stop()
|
|
|
|
os.execute("cat /tmp/dropbear_koreader.pid | xargs kill")
|
2021-05-31 19:18:04 +00:00
|
|
|
UIManager:show(InfoMessage:new {
|
|
|
|
text = T(_("SSH server stopped.")),
|
|
|
|
timeout = 2,
|
|
|
|
})
|
2018-07-15 16:39:52 +00:00
|
|
|
|
2023-04-05 06:39:56 +00:00
|
|
|
if self:isRunning() then
|
|
|
|
os.remove("/tmp/dropbear_koreader.pid")
|
|
|
|
end
|
|
|
|
|
2018-07-15 16:39:52 +00:00
|
|
|
-- Plug the hole in the Kindle's firewall
|
|
|
|
if Device:isKindle() then
|
|
|
|
os.execute(string.format("%s %s %s",
|
|
|
|
"iptables -D INPUT -p tcp --dport", self.SSH_port,
|
|
|
|
"-m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT"))
|
|
|
|
os.execute(string.format("%s %s %s",
|
|
|
|
"iptables -D OUTPUT -p tcp --sport", self.SSH_port,
|
|
|
|
"-m conntrack --ctstate ESTABLISHED -j ACCEPT"))
|
|
|
|
end
|
2018-05-13 19:27:52 +00:00
|
|
|
end
|
|
|
|
|
2021-05-31 19:18:04 +00:00
|
|
|
function SSH:onToggleSSHServer()
|
|
|
|
if self:isRunning() then
|
|
|
|
self:stop()
|
|
|
|
else
|
|
|
|
self:start()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function SSH:show_port_dialog(touchmenu_instance)
|
2018-05-13 19:27:52 +00:00
|
|
|
self.port_dialog = InputDialog:new{
|
|
|
|
title = _("Choose SSH port"),
|
|
|
|
input = self.SSH_port,
|
|
|
|
input_type = "number",
|
|
|
|
input_hint = self.SSH_port,
|
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
2022-03-04 20:20:00 +00:00
|
|
|
id = "close",
|
2018-05-13 19:27:52 +00:00
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.port_dialog)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Save"),
|
|
|
|
is_enter_default = true,
|
|
|
|
callback = function()
|
2021-05-31 19:18:04 +00:00
|
|
|
local value = tonumber(self.port_dialog:getInputText())
|
|
|
|
if value and value >= 0 then
|
2018-05-13 19:27:52 +00:00
|
|
|
self.SSH_port = value
|
|
|
|
G_reader_settings:saveSetting("SSH_port", self.SSH_port)
|
|
|
|
UIManager:close(self.port_dialog)
|
2021-05-31 19:18:04 +00:00
|
|
|
touchmenu_instance:updateItems()
|
2018-05-13 19:27:52 +00:00
|
|
|
end
|
2021-05-31 19:18:04 +00:00
|
|
|
end,
|
2018-05-13 19:27:52 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
UIManager:show(self.port_dialog)
|
|
|
|
self.port_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SSH:addToMainMenu(menu_items)
|
|
|
|
menu_items.ssh = {
|
|
|
|
text = _("SSH server"),
|
|
|
|
sub_item_table = {
|
|
|
|
{
|
2021-05-31 19:18:04 +00:00
|
|
|
text = _("SSH server"),
|
2018-09-04 21:55:58 +00:00
|
|
|
keep_menu_open = true,
|
2021-05-31 19:18:04 +00:00
|
|
|
checked_func = function() return self:isRunning() end,
|
2018-09-04 21:55:58 +00:00
|
|
|
callback = function(touchmenu_instance)
|
2021-05-31 19:18:04 +00:00
|
|
|
self:onToggleSSHServer()
|
2018-09-04 21:55:58 +00:00
|
|
|
-- sleeping might not be needed, but it gives the feeling
|
|
|
|
-- something has been done and feedback is accurate
|
|
|
|
ffiutil.sleep(1)
|
|
|
|
touchmenu_instance:updateItems()
|
|
|
|
end,
|
2018-05-13 19:27:52 +00:00
|
|
|
},
|
|
|
|
{
|
2021-05-31 19:18:04 +00:00
|
|
|
text_func = function()
|
|
|
|
return T(_("SSH port (%1)"), self.SSH_port)
|
|
|
|
end,
|
2018-09-04 21:55:58 +00:00
|
|
|
keep_menu_open = true,
|
2021-05-31 19:18:04 +00:00
|
|
|
enabled_func = function() return not self:isRunning() end,
|
2018-09-04 21:55:58 +00:00
|
|
|
callback = function(touchmenu_instance)
|
2021-05-31 19:18:04 +00:00
|
|
|
self:show_port_dialog(touchmenu_instance)
|
2018-09-04 21:55:58 +00:00
|
|
|
end,
|
2018-05-13 19:27:52 +00:00
|
|
|
},
|
|
|
|
{
|
2021-05-31 19:18:04 +00:00
|
|
|
text = _("SSH public key"),
|
2018-09-04 21:55:58 +00:00
|
|
|
keep_menu_open = true,
|
2018-05-13 19:27:52 +00:00
|
|
|
enabled_func = function() return not self:isRunning() end,
|
2021-05-31 19:18:04 +00:00
|
|
|
callback = function()
|
|
|
|
local info = InfoMessage:new{
|
|
|
|
timeout = 60,
|
|
|
|
text = T(_("Put your public SSH keys in\n%1"), BD.filepath(path.."/settings/SSH/authorized_keys")),
|
|
|
|
}
|
|
|
|
UIManager:show(info)
|
|
|
|
end,
|
2018-05-13 19:27:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Login without password (DANGEROUS)"),
|
|
|
|
checked_func = function() return self.allow_no_password end,
|
|
|
|
enabled_func = function() return not self:isRunning() end,
|
|
|
|
callback = function()
|
2021-05-31 19:18:04 +00:00
|
|
|
self.allow_no_password = not self.allow_no_password
|
|
|
|
G_reader_settings:flipNilOrFalse("SSH_allow_no_password")
|
2018-05-13 19:27:52 +00:00
|
|
|
end,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-05-31 19:18:04 +00:00
|
|
|
function SSH:onDispatcherRegisterActions()
|
2021-09-10 20:11:24 +00:00
|
|
|
Dispatcher:registerAction("toggle_ssh_server", { category = "none", event = "ToggleSSHServer", title = _("Toggle SSH server"), general=true})
|
2021-05-31 19:18:04 +00:00
|
|
|
end
|
|
|
|
|
2018-05-13 19:27:52 +00:00
|
|
|
return SSH
|