liminix

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

tftp.lua (21405B)


      1 -- this code is based on src/tftp.lua from https://github.com/ldrumm/tufty
      2 -- which is distributed under the MIT License
      3 -- Starting upstream revision 3cb95c869e2fe74cc61ca303d88af6c5daad6734
      4 --
      5 -- Changes made since then are mostly to make it work better with
      6 -- luasocket
      7 
      8 --[[
      9     This package provides intefaces for handling TFTP requests as specified by
     10     rfc1350, rfc1782, rfc2347, rfc2348 and (partial support for rfc2349)
     11     It should work on Standard PC servers as well as small home routers running
     12     OpenWRT or similar.
     13 ]]
     14 
     15 local tftp = {}
     16 local TIMEOUT = 5
     17 local ACK_RETRIES = 10
     18 local TFTP_PORT = 69
     19 local BLKSIZE = 512
     20 
     21 local OP_RRQ = 1
     22 local OP_WRQ = 2
     23 local OP_DATA = 3
     24 local OP_ACK = 4
     25 local OP_ERR = 5
     26 local OP_OACK = 6
     27 local ACKSIZE = 4
     28 
     29 local ERR_NOTFOUND = 1
     30 local ERR_ACCESS = 2
     31 local ERR_ALLOC = 3
     32 local ERR_ILLEGAL_OP = 4
     33 local ERR_UNKNOWN_ID = 5
     34 local ERR_EXISTS = 6
     35 local ERR_WHO = 7
     36 
     37 --rfc2347 specifies the options extension.
     38 --rfc2348 specifies the blksize extension
     39 --rfc2349 specifies the timeout and tsize extensions
     40 local SUPPORTED_OPTIONS = {blksize=true, timeout=true, tsize=true}
     41 
     42 local luasocket = require("socket")
     43 
     44 --Use penlight's prettyprinter if available
     45 pcall(require, 'pl')
     46 local log = pretty and pretty.dump or print
     47 
     48 local time = (function()
     49       return require("socket").gettime
     50 end)()
     51 
     52 local poll = (function()
     53     --[[
     54     ``poll`` is expected to accept a table of sockets keyed by
     55     backend file descriptor formatted as follows: {
     56         [low level socket lib file descriptor]{
     57             fd=(low level socket lib fd)
     58             wantread=(bool)
     59             wantwrite=(bool)
     60             ...arbitrary ignored extra fields
     61         },
     62         ...
     63     }
     64     it returns a list of fds formatted as follows
     65         {
     66              [low level socket lib file descriptor]{
     67                 fd= [low level socket lib file descriptor],
     68                 readable=(bool),
     69                 writable=(bool)
     70             }
     71         ...
     72         }
     73     ]]
     74 
     75     return function(fds, timeout)
     76         local wantread = {}
     77         local wantwrite = {}
     78         for _, fd  in pairs(fds) do
     79             fd.readable=false
     80             fd.writeable=false
     81             if fd.wantwrite then wantwrite[#wantwrite + 1] = fd.fd end
     82             if fd.wantread then wantread[#wantread + 1] = fd.fd end
     83         end
     84         local readable, writeable, timedout = luasocket.select(wantread, wantwrite, timeout)
     85         if timedout then return nil end
     86         local ready = {}
     87         for _, fd in ipairs(readable) do
     88 	   ready[fd] = ready[fd] or { fd = fd }
     89 	   ready[fd].readable = true
     90         end
     91         for _, fd in ipairs(writeable) do
     92 	   ready[fd] = ready[fd] or { fd = fd }
     93 	   ready[fd].writeable = true
     94         end
     95         return ready
     96     end
     97 end)()
     98 
     99 local function UDPSocket()
    100     --[[ We want to support the basic functionality required for TFTP operation over
    101         UDP.
    102         This wraps only the required functionality and in no way represents a
    103         complete UDP socket implementation.
    104         see http://w3.impa.br/~diego/software/luasocket/udp.html for the luasocket UDP API
    105     ]]
    106 
    107    return {
    108       fd = luasocket.udp(),
    109       bind = function(self, address, port)
    110 	 return self.fd:setsockname(address, port)
    111       end,
    112       sendto = function(self, data, address, port)
    113 	 return self.fd:sendto(data, address, port)
    114       end,
    115       recvfrom = function(self, length)
    116 	 return self.fd:receivefrom(length)
    117       end,
    118       close = function(self)
    119 	 return self.fd:close()
    120       end
    121    }
    122 end
    123 
    124 local function is_netascii(s)
    125     --[[Check whether a string contains only characters from the RFC764 ascii
    126     subset. see https://tools.ietf.org/html/rfc764#page-11
    127     ]]
    128     local ctrls = {[0]=1, [10]=1, [13]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1}
    129     for i=1, #s do
    130         local byte = s:sub(i, i):byte()
    131         if (byte < 31 and ctrls[byte] == nil) or byte > 127 then
    132             return false
    133         end
    134     end
    135     return true
    136 end
    137 
    138 local function create_opcode(val)
    139     if val < 0 or val > 2^16-1 then error("opcodes must fit into a 16bit integer") end
    140     local high = math.floor(val / 256)
    141     -- RFC1350 doesn't mention byte order.  Assume network order (big-endian).
    142     return string.char(high, val - (high * 256))
    143 end
    144 
    145 local function parse_opcode(packet)
    146     local opcode = string.byte(packet:sub(2, 2)) --assume big endian
    147     return ({"RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK"})[opcode]
    148 end
    149 
    150 function tftp:handle_RRQ(socket, host, port, source, options)
    151     local blksize = options and tonumber(options.blksize) or BLKSIZE
    152     local timeout_secs = options and tonumber(options.timeout) or TIMEOUT --rfc2349 timout option
    153     local length = options and tonumber(options.length)
    154     local tid = 1
    155     local time = time
    156     local started = time()
    157     local err = self.ERROR
    158     local done = false
    159     local error, success = error, error -- to terminate the coroutine immediately, we raise an error
    160     local yield = coroutine.yield
    161 
    162     return coroutine.create(function()
    163         if options then
    164         --[[The handler coroutine should not start running until we know the client is ready.
    165          This depends on whether the client has requested rfc2347 options and responded to an OACK.
    166          Without the options extension request, the client can be responded to immediately.]]
    167             local acked, timeout = false, time() + timeout_secs
    168             assert(socket:sendto(self.OACK(options), host, port))
    169             log(("sent OACK to %s:%d"):format(host, port))
    170             repeat
    171                 yield(true, false)
    172                 local msg, port, host = socket:recvfrom(ACKSIZE)
    173                 if self.parse_ACK(msg) == 0 then acked = true end
    174                 timedout = time() > timeout
    175             until acked or timedout
    176             if timedout then error("Request timed out waiting for OACK response") end
    177         end
    178         log(("coroutine started on %s:%s/"):format(host, port))
    179         while not done do
    180             if tid == 2^16 then
    181                 -- Roll over the tid. The specific rollover value is unspecified, but we
    182                 -- assume that the other side is U-Boot which rolls over to 0.
    183                 tid = 0
    184             end
    185             local okay, continue, data = pcall(source, blksize)
    186             if not okay then
    187                 packet = socket:sendto(err("An unknown error occurred"), host, port)
    188                 error("generator failure")
    189             end
    190             if data == nil and not continue then
    191                 done = true
    192             end
    193             if data == nil and continue then
    194                 --[[The generator ``source`` can be async and return `true, nil`
    195                     if no data is ready, but things are going well.
    196                 ]]
    197                 yield(false, true)
    198             end
    199 
    200             local acked
    201             local retried = 0
    202             local timeout = time() + timeout_secs
    203             local timedout = false
    204             repeat
    205                 socket:sendto(self.DATA(data, tid), host, port)
    206                 --[[  Now check for an ACK.
    207                    RFC1350 requires that for every packet sent, an ACK is received
    208                    before the next packet can be sent.
    209                 ]]
    210                 yield(true, false) -- we need to wait until the socket is readable again
    211                 local ack, ackhost, ackport = socket:recvfrom(ACKSIZE)
    212                 local ack_sequence = self.parse_ACK(ack)
    213 
    214                 if ackhost ~= host or ackport ~= port then
    215                    --[[https://tools.ietf.org/html/rfc1350#page-5
    216                        "If a source TID does not match, the packet should be
    217                        discarded as erroneously sent from somewhere else.
    218                        An error packet should be sent to the source of the
    219                        incorrect packet, while not disturbing the transfer."
    220                    ]]
    221                    socket:sendto(err(ERR_UNKNOWN_ID), ackhost, ackport)
    222                    yield(true, false)
    223 		elseif ack_sequence ~= tid then
    224 		   -- this looks confusing, but the local variable
    225 		   -- "tid" here is actually block number (aka
    226 		   -- sequence number), not tid at all.
    227 		   log(("ack received for old block %d (expecting %d)"):format(ack_sequence, tid))
    228 		   acked = false
    229 		else
    230 		   acked = true
    231 		end
    232 
    233 		if not acked then log("resending") end
    234                 retried = retried + 1
    235                 timedout = time() > timeout
    236             until acked or retried > ACK_RETRIES
    237             if retried > ACK_RETRIES then
    238                 --There doesn't seem to be a standard error for timeout.
    239                 socket:sendto(err("Ack timeout"), host, port)
    240                 error("Timeout waiting for ACK")
    241             end
    242             --Okay, we've been acked in reasonable time.
    243             tid = tid + 1
    244             if done then success() end
    245             yield(true, true)
    246         end
    247     end)
    248 end
    249 
    250 function tftp:handle_WRQ(socket, host, port, sink)
    251     error"Not Implemented"
    252 end
    253 
    254 function tftp:listen(rrq_generator_callback, wrq_generator_callback, hosts, port, logfile)
    255 --[[--
    256     Listen for TFTP requests on UDP ```bind``:`port`` (0.0.0.0:69 by default)
    257     and get data from / send data to user-generated source/sink functions.
    258     Data is generated/received by functions returned by the the user-supplied
    259     ``rrq_generator_callback``/``wrq_generator_callback`` factory functions.
    260     For each resource requested, the generator function will be called
    261     with three arguments:
    262        - the requested resource as a C-style string (no embedded NUL chars)
    263        - the ip address of the peer, as a dotted-quad string ("1.2.3.4")
    264        - the port number of the peer
    265     It should return a source or sink function that will be called repeatedly
    266     until the data transfer is complete:
    267         (SOURCE) will be called once for each block of data : it takes a
    268          single argument of the requested data length in bytes
    269          and returns the next block of data. It must return as follows
    270                 `true, data` on success
    271                 `true, nil` on wouldblock but should retry next round,
    272                 `false` on finished
    273         (SINK) takes two arguments
    274             ``data`` to write
    275             ``done`` (truthy), whether all data has been received and backends can cleanup.
    276 
    277     The (SOURCE) model therefore supports both blocking and non-blocking behaviour.
    278     If the given function blocks, however, it will block the whole process as Lua
    279     is single threaded. That may or may not be acceptable depending on your needs.
    280     If the requested resource is invalid or other termination conditions are met,
    281     (SOURCE) and (SINK) functions should raise an error.
    282     @return This method never returns unless interrupted.
    283 ]]
    284 
    285     local function create_handler(callbacks, request, requestsocket, host, port)
    286          --[[ Given a parsed request, instantiate the generator function from the given callbacks,
    287             and create a new coroutine to be called when the state of the handler's
    288             new socket changes to available.
    289             On success, returns a table of the form:
    290             ```{
    291                 handler=coroutine to call,
    292                 socket= new socket on a random port on which all new communication will happen,
    293                 fd=socket.fd as above fd
    294                 host=remote host,
    295                 port = remote port,
    296                 request = the original parsed request, including accepted options, if any.
    297             }```
    298             On error, responds to the client with an ERROR packet, and returns nil.
    299         ]]
    300         local okay, generator, tsize = pcall(callbacks[request.opcode], request.filename, host, port)
    301         if not okay then
    302             requestsocket:sendto(self.ERROR(ERR_NOTFOUND), host, port)
    303             return nil
    304         else
    305             if request.options then
    306                 request.options.tsize = request.options.tsize and tostring(tsize)
    307                 for k, v in pairs(request.options) do
    308                     if not SUPPORTED_OPTIONS[k] then request.options[k] = nil end
    309                 end
    310             else
    311                 --RFC1350 requires WRQ requests to be responded to with a zero TID before transfer commences,
    312                 --but when responding to an options request, it is dropped.
    313                 if request.opcode == 'WRQ' then requestsocket:sendto(self.ACK(0), host, port) end
    314             end
    315             local handlersocket = UDPSocket()
    316 	    handlersocket:bind("*", 0)
    317             local handler = self['handle_' .. request.opcode](self, handlersocket, host, port, generator, request.options)
    318             return {
    319                 handler=handler,
    320                 socket=handlersocket,
    321                 fd=handlersocket.fd,
    322                 host=host,
    323                 port=port,
    324                 request=request,
    325                 wantread=false,
    326                 wantwrite=true,
    327             }
    328         end
    329     end
    330 
    331     local function accept(socket)
    332         --[[ Read an incoming request from ``socket``, parse, and ACK as appropriate.
    333             If the request is invalid, responds to the client with error and returns `nil`
    334             otherwise returns the parsed request.
    335         ]]
    336         local msg, host, port = socket:recvfrom()
    337         if msg ~= false then
    338             local okay, xRQ = pcall(self.parse_XRQ, msg)
    339             if not okay then
    340                 return nil
    341             else
    342                 return host, port, xRQ
    343             end
    344         end
    345     end
    346 
    347     local socket = UDPSocket()
    348     local user_generator_callbacks = {RRQ=rrq_generator_callback, WRQ=wrq_generator_callback}
    349     local port = port or TFTP_PORT
    350     local logfile = logfile or io.stderr
    351     --listen on all given addresses, default to localhost if not given
    352     for i, address in pairs((type(hosts) == 'table' and hosts) or (hosts ~= nil and{hosts}) or {'127.0.0.1'}) do
    353        local ok, err = socket:bind(address, port)
    354        if not ok then error(err .. " binding to " .. address .. ":" .. port) end
    355     end
    356 
    357     --[[The main event loop does two things:
    358         1. Accepts new connections.
    359         2. Handles events occurring on all sockets by dispatching to a handler coroutine.
    360         3. Removes finished requests from the queue and destroys the sockets.
    361     ]]
    362     local handlers = {[socket.fd]={fd=socket.fd, socket=socket, listener=true, wantread=true}}
    363     while true do
    364         ready_fds = poll(handlers)
    365         do
    366             local n = 0
    367             for _ in pairs(ready_fds) do
    368                 n = n + 1
    369             end
    370 --            log(('There are %d sockets ready'):format(n))
    371         end
    372         for fd, status in pairs(ready_fds) do
    373 --            pretty.dump(ready_fds)
    374 --            log(('There are %d sockets ready'):format(#ready_fds))
    375             ready = handlers[fd]
    376             if ready.listener and status.readable then
    377                 --we've got a listener and should accept a new connection
    378                 local host, port, request = accept(ready.socket)
    379                 if host ~= nil then
    380                     log(("accepted new %s request - %s:%s/%s"):format(request.opcode, host, port, request.filename))
    381                     local handler = create_handler(
    382                         user_generator_callbacks,
    383                         request,
    384                         ready.socket,
    385                         host,
    386                         port
    387                     )
    388                     if handler then handlers[handler.socket.fd] = handler end
    389                 end
    390             elseif (status.readable or status.writeable) and ready.handler then
    391             --We've received an event on a socket associated with an existing handler coroutine.
    392                 local co_state = coroutine.status(ready.handler)
    393                 local okay, wantread, wantwrite
    394                 if co_state ~= 'dead' then
    395                     if (ready.wantread and status.readable) or (ready.wantwrite and status.writeable) then
    396                         okay, wantread, wantwrite = coroutine.resume(ready.handler)
    397                         ready.wantread = wantread
    398                         ready.wantwrite = wantwrite
    399                     end
    400                 end
    401                 if (not okay) or co_state == 'dead' then
    402                     --- the handler is finished; cleanup
    403                     ready.socket:close()
    404                     handlers[ready.fd] = nil
    405                     ready.fd = nil
    406                     ready = nil
    407                 end
    408             end
    409         end
    410     end
    411 end
    412 
    413 
    414 --[[ RRQ/ZRQ read/write request packets
    415     https://tools.ietf.org/html/rfc1350
    416     2 bytes     string    1 byte     string   1 byte
    417     ------------------------------------------------
    418    | Opcode |  Filename  |   0  |    Mode    |   0  |
    419     ------------------------------------------------
    420            Figure 5-1: RRQ/WRQ packet
    421 ]]
    422 function tftp.RRQ(filename)
    423 --  RFC1350:"The mail mode is obsolete and should not be implemented or used."
    424 --  We don't support netascii, which leaves 'octet' mode only
    425     return table.concat({create_opcode(OP_RRQ), filename, '\0', "octet", '\0'}, '')
    426 end
    427 
    428 function tftp.parse_XRQ(request)
    429     local opcode = assert(parse_opcode(request), "Invalid opcode")
    430     assert(({RRQ=true, XRQ=true})[opcode], "Not an xRQ")
    431     assert(request:sub(#request) == '\0', "Invalid request: expected ASCII NUL terminated request")
    432 
    433     local cstrings = {}
    434     function zero_iter(s)
    435         local pos = 1
    436         return function()
    437             --This is ugly. Lua 5.2 handles embedded NUL bytes in string.gmatch,
    438             --but vanilla Lua5.1 doesn't match correctly and luajit can't seem to parse them
    439             for i=pos, #s do
    440                 if s:byte(i) == 0 then
    441                     local sub = s:sub(pos, i-1)
    442                     pos = i+1
    443                     return sub
    444                 end
    445             end
    446         end
    447     end
    448     for s in zero_iter(request:sub(3)) do
    449         cstrings[#cstrings+1] = s
    450     end
    451     assert(#cstrings >= 2)
    452     local filename = assert(is_netascii(cstrings[1]) and cstrings[1], "Requested filename must be netascii")
    453     local mode = assert(({netascii='netascii', octet='octet'})[cstrings[2]])
    454     local options
    455     if #cstrings > 2 then
    456         options = {}
    457         assert(#cstrings % 2 == 0)
    458         for i=3, #cstrings, 2 do
    459             --[[ RFC1782, and 3247 require case insensitive comparisons.
    460                 We normalize them to lowercase with the consequence that
    461                 duplicate keys are replaced which are forbidden by the standard anyway.
    462             ]]
    463             options[cstrings[i]:lower()] = cstrings[i+1]:lower()
    464         end
    465     end
    466 
    467     return {opcode=opcode, filename=filename, mode=mode, options=options}
    468 end
    469 
    470 --[[ ACK functions
    471      2 bytes     2 bytes
    472      ---------------------
    473     | Opcode |   Block #  |
    474      ---------------------
    475      Figure 5-3: ACK packet
    476 ]]
    477 function tftp.parse_ACK(ack)
    478     --get the sequence number from an ACK or raise a error if not valid
    479     assert(#ack == ACKSIZE, "invalid ack")
    480     assert(parse_opcode(ack) == 'ACK', "invalid ack")
    481 
    482     -- extract the low and high order bytes and convert to an integer
    483     local high, low = ack:byte(3, 4)
    484     return (high * 256) + low
    485 end
    486 
    487 --[[
    488       +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
    489       |  opc  |  opt1  | 0 | value1 | 0 |  optN  | 0 | valueN | 0 |
    490       +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
    491 ]]
    492 function tftp.OACK(options)
    493     local stropts = {}
    494     for k, v in pairs(options) do
    495         assert(is_netascii(k))
    496         stropts[#stropts+1] =  k .. '\0' .. v .. '\0'
    497     end
    498     return create_opcode(OP_OACK) .. table.concat(stropts, '')
    499 end
    500 
    501 function tftp.ACK(tid)
    502     return table.concat({create_opcode(OP_ACK), create_opcode(tid)}, '')
    503 end
    504 
    505 
    506 --[[ DATA functions
    507    2 bytes     2 bytes      n bytes
    508    ----------------------------------
    509   | Opcode |   Block #  |   Data     |
    510    ----------------------------------
    511         Figure 5-2: DATA packet
    512 ]]
    513 function tftp.DATA(data, tid)
    514     local opcode = create_opcode(OP_DATA)
    515     local block = create_opcode(tid)
    516     return table.concat({opcode, block, data}, '')
    517 end
    518 
    519 function tftp.parse_DATA(data)
    520     assert(#data <= 512, "tftp data packets must be 512 bytes or less")
    521     assert(parse_opcode(data) == OP_DATA, "Invalid opcode")
    522     return {tid=parse_opcode(data:sub(3, 4)), data=data:sub(5)}
    523 end
    524 
    525 --[[ ERROR Functions
    526     2 bytes     2 bytes      string    1 byte
    527     -----------------------------------------
    528     | Opcode |  ErrorCode |   ErrMsg   |   0  |
    529     -----------------------------------------
    530         Figure 5-4: ERROR packet
    531 ]]
    532 function tftp.ERROR(err)
    533     local defined_errors = {
    534         --https://tools.ietf.org/html/rfc1350#page-10
    535         [0] = type(err) == 'string' and err or "Not defined",
    536         "File not found.",
    537         "Access violation.",
    538         "Disk full or allocation exceeded.",
    539         "Illegal TFTP operation.",
    540         "Unknown transfer ID.",
    541         "File already exists.",
    542         "No such user.",
    543     }
    544 
    545     local errno = type(err) == 'string' and 0 or err
    546     return table.concat({
    547         create_opcode(OP_ERR),
    548         create_opcode(errno),
    549         defined_errors[errno],
    550         '\0'
    551     }, '')
    552 end
    553 
    554 function tftp.parse_ERROR(err)
    555     assert(parse_opcode(err) == OP_ERR)
    556     local error_code = parse_opcode(err:sub(3, 4))
    557     return {errcode=error_code, errmsg=err:sub(5, #err-1)}
    558 end
    559 
    560 return tftp