mirror of
https://github.com/nodemcu/nodemcu-firmware.git
synced 2025-01-16 20:52:57 +08:00
parent
f20591a82e
commit
8d091c476e
50
docs/lua-modules/telnet.md
Normal file
50
docs/lua-modules/telnet.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Telnet Module
|
||||
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2018-05-24 | [Terry Ellison](https://github.com/TerryE) | [Terry Ellison](https://github.com/TerryE) | [telnet.lua](../../lua_modules/telnet/telnet.lua) |
|
||||
|
||||
The current version of this module exploits the stdin / stdout pipe functionality and
|
||||
task integration that is now build into the NodeNMCU Lua core.
|
||||
|
||||
There are two nice advantages of this core implementation:
|
||||
|
||||
- Errors are now written to stdout in a separate task execution.
|
||||
- The pipes pretty much eliminate UART and telnet overrun.
|
||||
|
||||
Both have the same interface if required into the variable `telnet`
|
||||
|
||||
## telnet:open()
|
||||
|
||||
Open a telnet server based on the provided parameters.
|
||||
|
||||
#### Syntax
|
||||
|
||||
`telnet:open(ssid, pwd, port)`
|
||||
|
||||
#### Parameters
|
||||
|
||||
`ssid` and `password`. Strings. SSID and Password for the Wifi network. If these are
|
||||
`nil` then the wifi is assumed to be configured or auto-configured.
|
||||
|
||||
`port`. Integer TCP listening port for the Telnet service. The default is 2323
|
||||
|
||||
#### Returns
|
||||
|
||||
Nothing returned (this is evaluated as `nil` in a scalar context).
|
||||
|
||||
## telnet:close()
|
||||
|
||||
Close a telnet server and release all resources. Also set the variable `telnet` to nil to fully reference and GC the resources.
|
||||
|
||||
#### Syntax
|
||||
|
||||
`telnet:close()`
|
||||
|
||||
#### Parameters
|
||||
|
||||
None
|
||||
|
||||
#### Returns
|
||||
|
||||
Nothing returned (this is evaluated as `nil` in a scalar context).
|
@ -1,56 +0,0 @@
|
||||
# Telnet Module
|
||||
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2014-12-22 | [Zeroday](https://github.com/funshine) | [Terry Ellison](https://github.com/TerryE) | [simple_telnet.lua](./simple_telnet.lua) |
|
||||
| 2018-05-24 | [Terry Ellison](https://github.com/TerryE) | [Terry Ellison](https://github.com/TerryE) | [telnet.lua](./telnet.lua) |
|
||||
|
||||
|
||||
This README discusses the packet marshalling versions of telnet. The first (fifosock)
|
||||
version was written for SDK 2 implementations, with all of the marshalling imlemented
|
||||
in Lua; the second (pipe) version uses the latest features added to the SDK 3 version
|
||||
that have been added to prepare for the `lua53` implementation. These exploit the
|
||||
stdin / stdout pipe functionality and task integration that is now build into the
|
||||
NodeNMCU Lua core.
|
||||
|
||||
There are two nice advantages of this core implementation:
|
||||
|
||||
- Errors are now written to stdout in a spearate task execution.
|
||||
- The pipes pretty much eliminate uart and telnet overrun.
|
||||
|
||||
Both have the same interface if required into the variable `telnet`
|
||||
|
||||
## telnet:open()
|
||||
|
||||
Open a telnet server based on the provided parameters.
|
||||
|
||||
#### Syntax
|
||||
|
||||
`telnet:open(ssid, pwd, port)`
|
||||
|
||||
#### Parameters
|
||||
|
||||
`ssid` and `password`. Strings. SSID and Password for the Wifi network. If these are
|
||||
`nil` then the wifi is assumed to be configured or autoconfigured.
|
||||
|
||||
`port`. Integer TCP listenting port for the Telnet service. The default is 2323
|
||||
|
||||
#### Returns
|
||||
|
||||
Nothing returned (this is evaluted as `nil` in a scalar context).
|
||||
|
||||
## telnet:close()
|
||||
|
||||
Close a telnet server and release all resources. Also set the variable `telnet` to nil to fully reference and GC the resources.
|
||||
|
||||
#### Syntax
|
||||
|
||||
`telnet:close()`
|
||||
|
||||
#### Parameters
|
||||
|
||||
None
|
||||
|
||||
#### Returns
|
||||
|
||||
Nothing returned (this is evaluted as `nil` in a scalar context).
|
@ -1,84 +0,0 @@
|
||||
--[[ A telnet server T. Ellison, May 2018
|
||||
|
||||
This version is more complex than the simple Lua example previously provided in our
|
||||
distro. The main reason is that a single Lua command can produce a LOT of output,
|
||||
and the server has to work within four constraints:
|
||||
|
||||
- The SDK rules are that you can only issue one send per task invocation, so any
|
||||
overflow must be buffered, and the buffer emptied using an on:sent cb
|
||||
|
||||
- Since the interpeter invokes a node.output cb per field, you have a double
|
||||
whammy that these fields are typically small, so using a simple array FIFO
|
||||
would rapidly exhaust RAM.
|
||||
|
||||
- For network efficiency, you want to aggregate any FIFO buffered into sensible
|
||||
sized packet, say 1024 bytes, but you also need to handle the case when larger
|
||||
string span multiple packets. However, you must flush the buffer if necessary.
|
||||
|
||||
- The overall buffering strategy needs to be reasonably memory efficient and avoid
|
||||
hitting the GC too hard, so where practical avoid aggregating small strings to
|
||||
more than 256 chars (as NodeMCU handles <256 using stack buffers), and avoid
|
||||
serial aggregation such as buf = buf .. str as this hammers the GC.
|
||||
|
||||
So this server adopts a simple buffering scheme using a two level FIFO. The node.output
|
||||
cb adds cb records to the 1st level FIFO until the #recs is > 32 or the total size
|
||||
would exceed 256 bytes. Once over this threashold, the contents of the FIFO are
|
||||
concatenated into a 2nd level FIFO entry of upto 256 bytes, and the 1st level FIFO
|
||||
cleared down to any residue.
|
||||
|
||||
]]
|
||||
--luacheck: no unused args
|
||||
|
||||
local node, tmr, wifi, uwrite = node, tmr, wifi, uart.write
|
||||
|
||||
local function telnet_listener(socket)
|
||||
local queueLine = (require "fifosock").wrap(socket)
|
||||
|
||||
local function receiveLine(s, line)
|
||||
node.input(line)
|
||||
end
|
||||
|
||||
local function disconnect(s)
|
||||
socket:on("disconnection", nil)
|
||||
socket:on("reconnection", nil)
|
||||
socket:on("connection", nil)
|
||||
socket:on("receive", nil)
|
||||
socket:on("sent", nil)
|
||||
node.output(nil)
|
||||
end
|
||||
|
||||
socket:on("receive", receiveLine)
|
||||
socket:on("disconnection", disconnect)
|
||||
node.output(queueLine, 0)
|
||||
print(("Welcome to NodeMCU world (%d mem free, %s)"):format(node.heap(), wifi.sta.getip()))
|
||||
end
|
||||
|
||||
local listenerSocket
|
||||
return {
|
||||
open = function(this, ssid, pwd, port)
|
||||
if ssid then
|
||||
wifi.setmode(wifi.STATION, false)
|
||||
wifi.sta.config { ssid = ssid, pwd = pwd, save = false }
|
||||
end
|
||||
local t = tmr.create()
|
||||
t:alarm(500, tmr.ALARM_AUTO, function()
|
||||
if (wifi.sta.status() == wifi.STA_GOTIP) then
|
||||
t:unregister()
|
||||
t=nil
|
||||
print(("Telnet server started (%d mem free, %s)"):format(node.heap(), wifi.sta.getip()))
|
||||
net.createServer(net.TCP, 180):listen(port or 23, telnet_listener)
|
||||
else
|
||||
uwrite(0,".")
|
||||
end
|
||||
end)
|
||||
end,
|
||||
|
||||
close = function(this)
|
||||
if listenerSocket then
|
||||
listenerSocket:close()
|
||||
package.loaded.telnet = nil
|
||||
listenerSocket = nil
|
||||
collectgarbage()
|
||||
end
|
||||
end,
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
--[[SPLIT MODULE ftp]]
|
||||
|
||||
--[[ A simple ftp server
|
||||
|
||||
This is my implementation of a FTP server using Github user Neronix's
|
||||
example as inspriration, but as a cleaner Lua implementation that has been
|
||||
optimised for use in LFS. The coding style adopted here is more similar to
|
||||
example as inspriration, but as a cleaner Lua implementation that is
|
||||
suitable for use in LFS. The coding style adopted here is more similar to
|
||||
best practice for normal (PC) module implementations, as using LFS enables
|
||||
me to bias towards clarity of coding over brevity. It includes extra logic
|
||||
to handle some of the edge case issues more robustly. It also uses a
|
||||
@ -13,113 +15,116 @@
|
||||
with any multiple calls requected, so FTP is a singleton static object.
|
||||
However there is nothing to stop multiple clients connecting to the FTP
|
||||
listener at the same time, and indeed some FTP clients do use multiple
|
||||
connections, so this server can accept and create multiple CON objects.
|
||||
Each CON object can also have a single DATA connection.
|
||||
connections, so this server can accept and create multiple cxt objects.
|
||||
Each cxt object can also have a single DATA connection.
|
||||
|
||||
Note that FTP also exposes a number of really private properties (which
|
||||
could be stores in local / upvals) as FTP properties for debug purposes.
|
||||
|
||||
Note that this version has now been updated to allow the main methods to
|
||||
be optionally loaded lazily, and SPILT comments allow the source to be
|
||||
preprocessed for loading as either components in the "fast" Cmodule or as
|
||||
LC files in SPIFFS.
|
||||
]]
|
||||
local file, net, wifi, node, table, tmr, pairs, print, pcall, tostring =
|
||||
file, net, wifi, node, table, tmr, pairs, print, pcall, tostring
|
||||
local post = node.task.post
|
||||
local FTP, cnt = {client = {}}, 0
|
||||
--luacheck: read globals fast file net node tmr uart wifi FAST_ftp SPIFFS_ftp
|
||||
|
||||
-- Local functions
|
||||
local FTP, FTPindex = {client = {}}, nil
|
||||
|
||||
local processCommand -- function(cxt, sock, data)
|
||||
local processBareCmds -- function(cxt, cmd)
|
||||
local processSimpleCmds -- function(cxt, cmd, arg)
|
||||
local processDataCmds -- function(cxt, cmd, arg)
|
||||
local dataServer -- function(cxt, n)
|
||||
local ftpDataOpen -- function(dataSocket)
|
||||
|
||||
-- Note these routines all used hoisted locals such as table and debug as
|
||||
-- upvals for performance (ROTable lookup is slow on NodeMCU Lua), but
|
||||
-- data upvals (e.g. FTP) are explicitly list is -- "upval:" comments.
|
||||
|
||||
-- Note that the space between debug and the arglist is there for a reason
|
||||
-- so that a simple global edit " debug(" -> "-- debug(" or v.v. to
|
||||
-- toggle debug compiled into the module.
|
||||
|
||||
local function debug (fmt, ...) -- upval: cnt (, print, node, tmr)
|
||||
if not FTP.debug then return end
|
||||
if (...) then fmt = fmt:format(...) end
|
||||
print(node.heap(),fmt)
|
||||
cnt = cnt + 1
|
||||
if cnt % 10 then tmr.wdclr() end
|
||||
if FAST_ftp then
|
||||
function FTPindex(_, name) return fast.load('ftp-'..name) end
|
||||
elseif SPIFFS_ftp then
|
||||
function FTPindex(_, name) return loadfile('ftp-'..name..'.lc') end
|
||||
end
|
||||
|
||||
if FTPindex then return setmetatable(FTP,{__index=FTPindex}) end
|
||||
|
||||
function FTP.open(...) --[[SPLIT HERE ftp-open]]
|
||||
--------------------------- Set up the FTP object ----------------------------
|
||||
-- FTP has three static methods: open, createServer and close
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
-- optional wrapper around createServer() which also starts the wifi session
|
||||
function FTP.open(user, pass, ssid, pwd, dbgFlag) -- upval: FTP (, wifi, tmr, print)
|
||||
-- Lua: FTP:open(user, pass, ssid, pwd[, dbgFlag])
|
||||
|
||||
local this, user, pass, ssid, pwd, dbgFlag = ...
|
||||
|
||||
if ssid then
|
||||
wifi.setmode(wifi.STATION, false)
|
||||
wifi.sta.config { ssid = ssid, pwd = pwd, save = false }
|
||||
end
|
||||
local t = tmr.create()
|
||||
t:alarm(500, tmr.ALARM_AUTO, function()
|
||||
tmr.create():alarm(500, tmr.ALARM_AUTO, function(t) -- this: FTP, user, pass, dbgFlag
|
||||
if (wifi.sta.status() == wifi.STA_GOTIP) then
|
||||
t:unregister()
|
||||
t=nil
|
||||
print("Welcome to NodeMCU world", node.heap(), wifi.sta.getip())
|
||||
return FTP.createServer(user, pass, dbgFlag)
|
||||
return this:createServer(user, pass, dbgFlag)
|
||||
else
|
||||
uart.write(0,".")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.createServer(...) --[[SPLIT HERE ftp-createServer]]
|
||||
-- Lua: FTP:createServer(user, pass[, dbgFlag])
|
||||
local this, user, pass, dbgFlag = ...
|
||||
local cnt = 0
|
||||
this.user, this.pass, dbgFlag = user, pass, (dbgFlag and true or false)
|
||||
|
||||
function FTP.createServer(user, pass, dbgFlag) -- upval: FTP (, debug, tostring, pcall, type, processCommand)
|
||||
FTP.user, FTP.pass, FTP.debug = user, pass, dbgFlag
|
||||
FTP.server = net.createServer(net.TCP, 180)
|
||||
_G.FTP = FTP
|
||||
debug("Server created: (userdata) %s", tostring(FTP.server))
|
||||
this.debug = (not dbgFlag) and type -- executing type(...) is in effect a NOOP
|
||||
or function(fmt, ...) -- upval: cnt
|
||||
if (...) then fmt = fmt:format(...) end
|
||||
print(node.heap(),fmt)
|
||||
cnt = cnt + 1
|
||||
if cnt % 10 then tmr.wdclr() end
|
||||
end
|
||||
|
||||
FTP.server:listen(21, function(sock) -- upval: FTP (, debug, pcall, type, processCommand)
|
||||
this.server = net.createServer(net.TCP, 180)
|
||||
_G.FTP = this
|
||||
this.debug("Server created: (userdata) %s", tostring(this.server))
|
||||
|
||||
this.server:listen(21, function(sock) -- upval: this
|
||||
-- since a server can have multiple connections, each connection
|
||||
-- has a CNX table to store connection-wide globals.
|
||||
local client = FTP.client
|
||||
local CNX; CNX = {
|
||||
-- has its own CXN object (table) to store connection-wide globals.
|
||||
local CXN; CXN = {
|
||||
validUser = false,
|
||||
cmdSocket = sock,
|
||||
send = function(rec, cb) -- upval: CNX (,debug)
|
||||
-- debug("Sending: %s", rec)
|
||||
return CNX.cmdSocket:send(rec.."\r\n", cb)
|
||||
end, --- send()
|
||||
close = function(socket) -- upval: client, CNX (,debug, pcall, type)
|
||||
-- debug("Closing CNX.socket=%s, sock=%s", tostring(CNX.socket), tostring(sock))
|
||||
debug = this.debug,
|
||||
FTP = this,
|
||||
send = function(rec, cb) -- upval: CXN
|
||||
CXN.debug("Sending: %s", rec)
|
||||
return CXN.cmdSocket:send(rec.."\r\n", cb)
|
||||
end, --- CXN. send()
|
||||
close = function(socket) -- upval: CXN
|
||||
CXN.debug("Closing CXN.cmdSocket=%s", tostring(CXN.cmdSocket))
|
||||
for _,s in ipairs{'cmdSocket', 'dataServer', 'dataSocket'} do
|
||||
local sck; sck,CNX[s] = CNX[s], nil
|
||||
-- debug("closing CNX.%s=%s", s, tostring(sck))
|
||||
if type(sck)=='userdata' then pcall(sck.close, sck) end
|
||||
CXN.debug("closing CXN.%s=%s", s, tostring(CXN[s]))
|
||||
if type(CXN[s])=='userdata' then
|
||||
pcall(socket.close, CXN[s])
|
||||
CXN[s]= nil
|
||||
end
|
||||
client[socket] = nil
|
||||
end -- CNX.close()
|
||||
end
|
||||
CXN.FTP.client[socket] = nil
|
||||
end -- CXN.close()
|
||||
}
|
||||
|
||||
local function validateUser(socket, data) -- upval: CNX, FTP (, debug, processCommand)
|
||||
local function validateUser(socket, data) -- upval: CXN
|
||||
-- validate the logon and if then switch to processing commands
|
||||
|
||||
-- debug("Authorising: %s", data)
|
||||
CXN.debug("Authorising: %s", data)
|
||||
local cmd, arg = data:match('([A-Za-z]+) *([^\r\n]*)')
|
||||
local msg = "530 Not logged in, authorization required"
|
||||
cmd = cmd:upper()
|
||||
|
||||
if cmd == 'USER' then
|
||||
CNX.validUser = (arg == FTP.user)
|
||||
msg = CNX.validUser and
|
||||
CXN.validUser = (arg == CXN.FTP.user)
|
||||
msg = CXN.validUser and
|
||||
"331 OK. Password required" or
|
||||
"530 user not found"
|
||||
|
||||
elseif CNX.validUser and cmd == 'PASS' then
|
||||
if arg == FTP.pass then
|
||||
CNX.cwd = '/'
|
||||
socket:on("receive", function(socketObj, dataObj)
|
||||
processCommand(CNX,socketObj, dataObj)
|
||||
elseif CXN.validUser and cmd == 'PASS' then
|
||||
if arg == CXN.FTP.pass then
|
||||
CXN.cwd = '/'
|
||||
socket:on("receive", function(soc, rec) -- upval: CXN
|
||||
assert(soc==CXN.cmdSocket)
|
||||
CXN.FTP.processCommand(CXN, rec)
|
||||
end) -- logged on so switch to command mode
|
||||
msg = "230 Login successful. Username & password correct; proceed."
|
||||
else
|
||||
@ -128,46 +133,45 @@ function FTP.createServer(user, pass, dbgFlag) -- upval: FTP (, debug, tostring
|
||||
|
||||
elseif cmd == 'AUTH' then
|
||||
msg = "500 AUTH not understood"
|
||||
|
||||
end
|
||||
|
||||
return CNX.send(msg)
|
||||
return CXN.send(msg)
|
||||
end
|
||||
|
||||
local port,ip = sock:getpeer() -- luacheck: no unused
|
||||
--debug("Connection accepted: (userdata) %s client %s:%u", tostring(sock), ip, port)
|
||||
--cxt.debug("Connection accepted: (userdata) %s client %s:%u", tostring(sock), ip, port)
|
||||
sock:on("receive", validateUser)
|
||||
sock:on("disconnection", CNX.close)
|
||||
FTP.client[sock]=CNX
|
||||
sock:on("disconnection", CXN.close)
|
||||
this.client[sock]=CXN
|
||||
|
||||
CNX.send("220 FTP server ready");
|
||||
end) -- FTP.server:listen()
|
||||
end -- FTP.createServer()
|
||||
CXN.send("220 FTP server ready");
|
||||
end) -- this.server:listen()
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.close(...) --[[SPLIT HERE ftp-close]]
|
||||
-- Lua: FTP:close()
|
||||
|
||||
local this = ...
|
||||
|
||||
function FTP.close() -- upval: FTP (, debug, post, tostring)
|
||||
local svr = FTP.server
|
||||
|
||||
local function rollupClients(client, server) -- upval: FTP (,debug, post, tostring, rollupClients)
|
||||
-- this is done recursively so that we only close one client per task
|
||||
local skt,cxt = next(client)
|
||||
-- this.client is a table of soc = cnx. The first (and usually only connection) is cleared
|
||||
-- immediately and next() used to do a post chain so we only close one client per task
|
||||
local function rollupClients(skt,cxt) -- upval: this, rollupClients
|
||||
if skt then
|
||||
-- debug("Client close: %s", tostring(skt))
|
||||
this.debug("Client close: %s", tostring(skt))
|
||||
cxt.close(skt)
|
||||
post(function() return rollupClients(client, server) end) -- upval: rollupClients, client, server
|
||||
else
|
||||
-- debug("Server close: %s", tostring(server))
|
||||
server:close()
|
||||
server:__gc()
|
||||
FTP,_G.FTP = nil, nil -- the upval FTP can only be zeroed once FTP.client is cleared.
|
||||
this.client[skt] = nil
|
||||
node.task.post(function() return rollupClients(next(this.client, skt)) end) -- upval: rollupClients, this, skt
|
||||
else -- we have emptied the open socket table, so can now shut the server
|
||||
this.debug("Server close: %s", tostring(this. server))
|
||||
this.server:close()
|
||||
this.server:__gc()
|
||||
_G.FTP = nil
|
||||
end
|
||||
end
|
||||
rollupClients(next(this.client))
|
||||
package.loaded.ftpserver = nil
|
||||
|
||||
if svr then rollupClients(FTP.client, svr) end
|
||||
package.loaded.ftpserver=nil
|
||||
end -- FTP.close()
|
||||
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.processCommand(...) --[[SPLIT HERE ftp-processCommand]]
|
||||
----------------------------- Process Command --------------------------------
|
||||
-- This splits the valid commands into one of three categories:
|
||||
-- * bare commands (which take no arg)
|
||||
@ -177,28 +181,30 @@ end -- FTP.close()
|
||||
--
|
||||
-- Find strings are used do this lookup and minimise long if chains.
|
||||
------------------------------------------------------------------------------
|
||||
-- upvals: (, debug, processBareCmds, processSimpleCmds, processDataCmds)
|
||||
processCommand = function(cxt, socket, data) -- luacheck: no unused
|
||||
debug("Command: %s", data)
|
||||
|
||||
local cxt, data = ...
|
||||
|
||||
cxt.debug("Command: %s", data)
|
||||
data = data:gsub('[\r\n]+$', '') -- chomp trailing CRLF
|
||||
local cmd, arg = data:match('([a-zA-Z]+) *(.*)')
|
||||
cmd = cmd:upper()
|
||||
local _cmd_ = '_'..cmd..'_'
|
||||
|
||||
if ('_CDUP_NOOP_PASV_PWD_QUIT_SYST_'):find(_cmd_) then
|
||||
processBareCmds(cxt, cmd)
|
||||
cxt.FTP.processBareCmds(cxt, cmd)
|
||||
elseif ('_CWD_DELE_MODE_PORT_RNFR_RNTO_SIZE_TYPE_'):find(_cmd_) then
|
||||
processSimpleCmds(cxt, cmd, arg)
|
||||
cxt.FTP.processSimpleCmds(cxt, cmd, arg)
|
||||
elseif ('_LIST_NLST_RETR_STOR_'):find(_cmd_) then
|
||||
processDataCmds(cxt, cmd, arg)
|
||||
cxt.FTP.processDataCmds(cxt, cmd, arg)
|
||||
else
|
||||
cxt.send("500 Unknown error")
|
||||
end
|
||||
end -- processCommand(sock, data)
|
||||
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.processBareCmds(...) --[[SPLIT HERE ftp-processBareCmds]]
|
||||
-------------------------- Process Bare Commands -----------------------------
|
||||
processBareCmds = function(cxt, cmd) -- upval: (dataServer)
|
||||
|
||||
local cxt, cmd = ...
|
||||
|
||||
|
||||
local send = cxt.send
|
||||
|
||||
@ -220,7 +226,7 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
|
||||
pplo = port % 256
|
||||
pphi = (port-pplo)/256
|
||||
i1,i2,i3,i4 = ip:match("(%d+).(%d+).(%d+).(%d+)")
|
||||
dataServer(cxt, port)
|
||||
cxt.FTP.dataServer(cxt, port)
|
||||
return send(
|
||||
('227 Entering Passive Mode(%d,%d,%d,%d,%d,%d)'):format(
|
||||
i1,i2,i3,i4,pphi,pplo))
|
||||
@ -229,7 +235,7 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
|
||||
return send('257 "/" is the current directory')
|
||||
|
||||
elseif cmd == 'QUIT' then
|
||||
send("221 Goodbye", function() cxt.close(cxt.cmdSocket) end)
|
||||
send("221 Goodbye", function() cxt.close(cxt.cmdSocket) end) -- upval: cxt
|
||||
return
|
||||
|
||||
elseif cmd == 'SYST' then
|
||||
@ -239,11 +245,14 @@ processBareCmds = function(cxt, cmd) -- upval: (dataServer)
|
||||
else
|
||||
error('Oops. Missed '..cmd)
|
||||
end
|
||||
end -- processBareCmds(cmd, send)
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.processSimpleCmds(...) --[[SPLIT HERE ftp-processSimpleCmds]]
|
||||
|
||||
------------------------- Process Simple Commands ----------------------------
|
||||
local from -- needs to persist between simple commands
|
||||
processSimpleCmds = function(cxt, cmd, arg) -- upval: from (, file, tostring, dataServer, debug)
|
||||
|
||||
local cxt, cmd, arg = ...
|
||||
|
||||
|
||||
local send = cxt.send
|
||||
|
||||
@ -252,7 +261,7 @@ processSimpleCmds = function(cxt, cmd, arg) -- upval: from (, file, tostring, d
|
||||
"504 Only S(tream) is suported")
|
||||
|
||||
elseif cmd == 'PORT' then
|
||||
dataServer(cxt,nil) -- clear down any PASV setting
|
||||
cxt.FTP.dataServer(cxt,nil) -- clear down any PASV setting
|
||||
return send("502 Active mode not supported. PORT not implemented")
|
||||
|
||||
elseif cmd == 'TYPE' then
|
||||
@ -269,7 +278,7 @@ processSimpleCmds = function(cxt, cmd, arg) -- upval: from (, file, tostring, d
|
||||
|
||||
-- The remaining commands take a filename as an arg. Strip off leading / and ./
|
||||
arg = arg:gsub('^%.?/',''):gsub('^%.?/','')
|
||||
debug("Filename is %s",arg)
|
||||
cxt.debug("Filename is %s",arg)
|
||||
|
||||
if cmd == 'CWD' then
|
||||
if arg:match('^[%./]*$') then
|
||||
@ -285,14 +294,14 @@ processSimpleCmds = function(cxt, cmd, arg) -- upval: from (, file, tostring, d
|
||||
return send("550 Requested action not taken")
|
||||
|
||||
elseif cmd == 'RNFR' then
|
||||
from = arg
|
||||
cxt.from = arg
|
||||
send("350 RNFR accepted")
|
||||
return
|
||||
|
||||
elseif cmd == 'RNTO' then
|
||||
local status = from and file.rename(from, arg)
|
||||
-- debug("rename('%s','%s')=%s", tostring(from), tostring(arg), tostring(status))
|
||||
from = nil
|
||||
local status = cxt.from and file.rename(cxt.from, arg)
|
||||
cxt.debug("rename('%s','%s')=%s", tostring(cxt.from), tostring(arg), tostring(status))
|
||||
cxt.from = nil
|
||||
return send(status and "250 File renamed" or
|
||||
"550 Requested action not taken")
|
||||
elseif cmd == "SIZE" then
|
||||
@ -303,16 +312,19 @@ processSimpleCmds = function(cxt, cmd, arg) -- upval: from (, file, tostring, d
|
||||
else
|
||||
error('Oops. Missed '..cmd)
|
||||
end
|
||||
end -- processSimpleCmds(cmd, arg, send)
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.processDataCmds(...) --[[SPLIT HERE ftp-processDataCmds]]
|
||||
|
||||
-------------------------- Process Data Commands -----------------------------
|
||||
processDataCmds = function(cxt, cmd, arg) -- upval: FTP (, pairs, file, tostring, debug, post)
|
||||
|
||||
local send = cxt.send
|
||||
local cxt, cmd, arg = ...
|
||||
|
||||
|
||||
local send, FTP = cxt.send, cxt.FTP -- luacheck: ignore FTP
|
||||
|
||||
-- The data commands are only accepted if a PORT command is in scope
|
||||
if cxt.dataServer == nil and cxt.dataSocket == nil then
|
||||
if FTP.dataServer == nil and cxt.dataSocket == nil then
|
||||
return send("502 Active mode not supported. "..cmd.." not implemented")
|
||||
end
|
||||
|
||||
@ -340,8 +352,8 @@ processDataCmds = function(cxt, cmd, arg) -- upval: FTP (, pairs, file, tostrin
|
||||
end
|
||||
table.sort(nameList)
|
||||
|
||||
function cxt.getData() -- upval: cmd, fileSize, nameList (, table)
|
||||
local list, user = {}, FTP.user
|
||||
function cxt.getData(c) -- upval: cmd, fileSize, nameList
|
||||
local list, user = {}, c.FTP.user
|
||||
for i = 1,10 do -- luacheck: no unused
|
||||
if #nameList == 0 then break end
|
||||
local f = table.remove(nameList, 1)
|
||||
@ -355,7 +367,7 @@ processDataCmds = function(cxt, cmd, arg) -- upval: FTP (, pairs, file, tostrin
|
||||
elseif cmd == "RETR" then
|
||||
local f = file.open(arg, "r")
|
||||
if f then -- define a getter to read the file
|
||||
function cxt.getData() -- upval: f
|
||||
function cxt.getData(c) -- luacheck: ignore c -- upval: f
|
||||
local buf = f:read(1024)
|
||||
if not buf then f:close(); f = nil; end
|
||||
return buf
|
||||
@ -365,13 +377,13 @@ processDataCmds = function(cxt, cmd, arg) -- upval: FTP (, pairs, file, tostrin
|
||||
elseif cmd == "STOR" then
|
||||
local f = file.open(arg, "w")
|
||||
if f then -- define a setter to write the file
|
||||
function cxt.setData(rec) -- upval f, arg (, debug)
|
||||
-- debug("writing %u bytes to %s", #rec, arg)
|
||||
function cxt.setData(c, rec) -- luacheck: ignore c -- upval: f (, arg)
|
||||
cxt.debug("writing %u bytes to %s", #rec, arg)
|
||||
return f:write(rec)
|
||||
end -- cxt.saveData(rec)
|
||||
function cxt.fileClose() -- upval cxt, f, arg (,debug)
|
||||
-- debug("closing %s", arg)
|
||||
f:close(); cxt.fileClose, f = nil, nil
|
||||
function cxt.fileClose(c) -- luacheck: ignore c -- upval: f (,arg)
|
||||
cxt.debug("closing %s", arg)
|
||||
f:close(); f = nil
|
||||
end -- cxt.close()
|
||||
end
|
||||
|
||||
@ -380,13 +392,12 @@ processDataCmds = function(cxt, cmd, arg) -- upval: FTP (, pairs, file, tostrin
|
||||
send((cxt.getData or cxt.setData) and "150 Accepted data connection" or
|
||||
"451 Can't open/create "..arg)
|
||||
if cxt.getData and cxt.dataSocket then
|
||||
debug ("poking sender to initiate first xfer")
|
||||
post(function() cxt.sender(cxt.dataSocket) end)
|
||||
cxt.debug ("poking sender to initiate first xfer")
|
||||
node.task.post(function() cxt.sender(cxt.dataSocket) end) -- upval: cxt
|
||||
end
|
||||
|
||||
end -- processDataCmds(cmd, arg, send)
|
||||
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.dataServer(...) --[[SPLIT HERE ftp-dataServer]]
|
||||
----------------------------- Data Port Routines -----------------------------
|
||||
-- These are used to manage the data transfer over the data port. This is
|
||||
-- set up lazily either by a PASV or by the first LIST NLST RETR or STOR
|
||||
@ -394,51 +405,53 @@ end -- processDataCmds(cmd, arg, send)
|
||||
-- handle the actual xfer. Also note that the sending process can be primed in
|
||||
--
|
||||
---------------- Open a new data server and port ---------------------------
|
||||
dataServer = function(cxt, n) -- upval: (pcall, net, ftpDataOpen, debug, tostring)
|
||||
local dataSrv = cxt.dataServer
|
||||
if dataSrv then -- close any existing listener
|
||||
pcall(dataSrv.close, dataSrv)
|
||||
end
|
||||
|
||||
local cxt, n = ...
|
||||
|
||||
local dataSvr = cxt.dataServer
|
||||
if dataSvr then pcall(dataSvr.close, dataSrv) end -- luacheck: ignore -- close any existing listener
|
||||
if n then
|
||||
-- Open a new listener if needed. Note that this is only used to establish
|
||||
-- a single connection, so ftpDataOpen closes the server socket
|
||||
cxt.dataServer = net.createServer(net.TCP, 300)
|
||||
cxt.dataServer:listen(n, function(sock) -- upval: cxt, (ftpDataOpen)
|
||||
ftpDataOpen(cxt,sock)
|
||||
dataSvr = net.createServer(net.TCP, 300)
|
||||
cxt.dataServer = dataSvr
|
||||
dataSvr:listen(n, function(sock) -- upval: cxt
|
||||
cxt.FTP.ftpDataOpen(cxt,sock)
|
||||
end)
|
||||
-- debug("Listening on Data port %u, server %s",n, tostring(cxt.dataServer))
|
||||
cxt.debug("Listening on Data port %u, server %s",n, tostring(cxt.dataServer))
|
||||
else
|
||||
cxt.dataServer = nil
|
||||
-- debug("Stopped listening on Data port",n)
|
||||
cxt.debug("Stopped listening on Data port",n)
|
||||
end
|
||||
end -- dataServer(n)
|
||||
|
||||
end --[[SPLIT IGNORE]]
|
||||
function FTP.ftpDataOpen(...) --[[SPLIT HERE ftp-ftpDataOpen]]
|
||||
----------------------- Connection on FTP data port ------------------------
|
||||
ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
|
||||
|
||||
local cxt, dataSocket = ...
|
||||
|
||||
local sport,sip = dataSocket:getaddr()
|
||||
local cport,cip = dataSocket:getpeer()
|
||||
debug("Opened data socket %s from %s:%u to %s:%u", tostring(dataSocket),sip,sport,cip,cport )
|
||||
cxt.debug("Opened data socket %s from %s:%u to %s:%u", tostring(dataSocket),sip,sport,cip,cport )
|
||||
cxt.dataSocket = dataSocket
|
||||
|
||||
cxt.dataServer:close()
|
||||
cxt.dataServer = nil
|
||||
|
||||
local function cleardown(skt,type) -- upval: cxt (, debug, tostring, post, pcall)
|
||||
-- luacheck: push no unused
|
||||
type = type==1 and "disconnection" or "reconnection"
|
||||
function cxt.cleardown(cxt, skt, cdtype) --luacheck: ignore cxt -- shadowing
|
||||
-- luacheck: ignore cdtype which
|
||||
cdtype = cdtype==1 and "disconnection" or "reconnection"
|
||||
local which = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")
|
||||
--debug("Cleardown entered from %s with %s", type, which)
|
||||
-- luacheck: pop
|
||||
cxt.debug("Cleardown entered from %s with %s", cdtype, which)
|
||||
if cxt.setData then
|
||||
cxt.fileClose()
|
||||
cxt:fileClose()
|
||||
cxt.setData = nil
|
||||
cxt.send("226 Transfer complete.")
|
||||
else
|
||||
cxt.getData, cxt.sender = nil, nil
|
||||
end
|
||||
-- debug("Clearing down data socket %s", tostring(skt))
|
||||
post(function() -- upval: skt, cxt, (, pcall)
|
||||
cxt.debug("Clearing down data socket %s", tostring(skt))
|
||||
node.task.post(function() -- upval: cxt, skt
|
||||
pcall(skt.close, skt); skt=nil
|
||||
cxt.dataSocket = nil
|
||||
end)
|
||||
@ -446,10 +459,10 @@ ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
|
||||
|
||||
local on_hold = false
|
||||
|
||||
dataSocket:on("receive", function(skt, rec) --upval: cxt, on_hold (, debug, tstring, post, node, pcall)
|
||||
dataSocket:on("receive", function(skt, rec) -- upval: cxt, on_hold
|
||||
|
||||
local which = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")-- luacheck: no unused
|
||||
--debug("Received %u data bytes with %s", #rec, which)
|
||||
local rectype = cxt.setData and "setData" or (cxt.getData and cxt.getData or "neither")
|
||||
cxt.debug("Received %u data bytes with %s", #rec, rectype)
|
||||
|
||||
if not cxt.setData then return end
|
||||
|
||||
@ -458,33 +471,33 @@ ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
|
||||
-- large file. As soon as a record arrives assert a flow control hold.
|
||||
-- This can take up to 5 packets to come into effect at which point the
|
||||
-- low priority unhold task is executed releasing the flow again.
|
||||
-- debug("Issuing hold on data socket %s", tostring(skt))
|
||||
cxt.debug("Issuing hold on data socket %s", tostring(skt))
|
||||
skt:hold(); on_hold = true
|
||||
post(node.task.LOW_PRIORITY,
|
||||
function() -- upval: skt, on_hold (, debug, tostring))
|
||||
-- debug("Issuing unhold on data socket %s", tostring(skt))
|
||||
node.task.post(node.task.LOW_PRIORITY,
|
||||
function() -- upval: skt, on_hold
|
||||
cxt.debug("Issuing unhold on data socket %s", tostring(skt))
|
||||
pcall(skt.unhold, skt); on_hold = false
|
||||
end)
|
||||
end
|
||||
|
||||
if not cxt.setData(rec) then
|
||||
-- debug("Error writing to SPIFFS")
|
||||
cxt.fileClose()
|
||||
if not cxt:setData(rec) then
|
||||
cxt.debug("Error writing to SPIFFS")
|
||||
cxt:fileClose()
|
||||
cxt.setData = nil
|
||||
cxt.send("552 Upload aborted. Exceeded storage allocation")
|
||||
end
|
||||
end)
|
||||
|
||||
function cxt.sender(skt) -- upval: cxt (, debug)
|
||||
debug ("entering sender")
|
||||
function cxt.sender(skt) -- upval: cxt
|
||||
cxt.debug ("entering sender")
|
||||
if not cxt.getData then return end
|
||||
skt = skt or cxt.dataSocket
|
||||
local rec = cxt.getData()
|
||||
local rec = cxt:getData()
|
||||
if rec and #rec > 0 then
|
||||
-- debug("Sending %u data bytes", #rec)
|
||||
cxt.debug("Sending %u data bytes", #rec)
|
||||
skt:send(rec)
|
||||
else
|
||||
-- debug("Send of data completed")
|
||||
cxt.debug("Send of data completed")
|
||||
skt:close()
|
||||
cxt.send("226 Transfer complete.")
|
||||
cxt.getData, cxt.dataSocket = nil, nil
|
||||
@ -492,14 +505,11 @@ ftpDataOpen = function(cxt, dataSocket) -- upval: (debug, tostring, post, pcall)
|
||||
end
|
||||
|
||||
dataSocket:on("sent", cxt.sender)
|
||||
dataSocket:on("disconnection", function(skt) return cleardown(skt,1) end)
|
||||
dataSocket:on("reconnection", function(skt) return cleardown(skt,2) end)
|
||||
dataSocket:on("disconnection", function(skt) return cxt:cleardown(skt,1) end) -- upval: cxt
|
||||
dataSocket:on("reconnection", function(skt) return cxt:cleardown(skt,2) end) -- upval: cxt
|
||||
|
||||
-- if we are sending to client then kick off the first send
|
||||
if cxt.getData then cxt.sender(cxt.dataSocket) end
|
||||
|
||||
end -- ftpDataOpen(socket)
|
||||
|
||||
------------------------------------------------ -----------------------------
|
||||
|
||||
return FTP
|
||||
end --[[SPLIT HERE]]
|
||||
return FTP --[[SPLIT IGNORE]]
|
||||
|
3
lua_modules/telnet/README.md
Normal file
3
lua_modules/telnet/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Telnet Module
|
||||
|
||||
Documentation for this Lua module is available in the [telnet.md](../../docs/lua-modules/telnet.md) file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section.
|
@ -1,3 +1,5 @@
|
||||
--[[SPLIT MODULE telnet]]
|
||||
|
||||
--[[ A telnet server T. Ellison, June 2019
|
||||
|
||||
This version of the telnet server demonstrates the use of the new stdin and stout
|
||||
@ -65,5 +67,4 @@ function M.close(this)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
||||
--[[SPLIT HERE]]
|
@ -58,6 +58,7 @@ pages:
|
||||
- 'lm92': 'lua-modules/lm92.md'
|
||||
- 'mcp23008': 'lua-modules/mcp23008.md'
|
||||
- 'redis': 'lua-modules/redis.md'
|
||||
- 'telnet': 'lua-modules/telnet.md'
|
||||
- 'yeelink': 'lua-modules/yeelink.md'
|
||||
- C Modules:
|
||||
- 'adc': 'modules/adc.md'
|
||||
|
Loading…
x
Reference in New Issue
Block a user