liminix

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

tftp.lua (21316B)


      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                 socket:sendto(err("File too big."), host, port)
    182                 error("File too big.")
    183             end
    184             local okay, continue, data = pcall(source, blksize)
    185             if not okay then
    186                 packet = socket:sendto(err("An unknown error occurred"), host, port)
    187                 error("generator failure")
    188             end
    189             if data == nil and not continue then
    190                 done = true
    191             end
    192             if data == nil and continue then
    193                 --[[The generator ``source`` can be async and return `true, nil`
    194                     if no data is ready, but things are going well.
    195                 ]]
    196                 yield(false, true)
    197             end
    198 
    199             local acked
    200             local retried = 0
    201             local timeout = time() + timeout_secs
    202             local timedout = false
    203             repeat
    204                 socket:sendto(self.DATA(data, tid), host, port)
    205                 --[[  Now check for an ACK.
    206                    RFC1350 requires that for every packet sent, an ACK is received
    207                    before the next packet can be sent.
    208                 ]]
    209                 yield(true, false) -- we need to wait until the socket is readable again
    210                 local ack, ackhost, ackport = socket:recvfrom(ACKSIZE)
    211                 local ack_sequence = self.parse_ACK(ack)
    212 
    213                 if ackhost ~= host or ackport ~= port then
    214                    --[[https://tools.ietf.org/html/rfc1350#page-5
    215                        "If a source TID does not match, the packet should be
    216                        discarded as erroneously sent from somewhere else.
    217                        An error packet should be sent to the source of the
    218                        incorrect packet, while not disturbing the transfer."
    219                    ]]
    220                    socket:sendto(err(ERR_UNKNOWN_ID), ackhost, ackport)
    221                    yield(true, false)
    222 		elseif ack_sequence ~= tid then
    223 		   -- this looks confusing, but the local variable
    224 		   -- "tid" here is actually block number (aka
    225 		   -- sequence number), not tid at all.
    226 		   log(("ack received for old block %d (expecting %d)"):format(ack_sequence, tid))
    227 		   acked = false
    228 		else
    229 		   acked = true
    230 		end
    231 
    232 		if not acked then log("resending") end
    233                 retried = retried + 1
    234                 timedout = time() > timeout
    235             until acked or retried > ACK_RETRIES
    236             if retried > ACK_RETRIES then
    237                 --There doesn't seem to be a standard error for timeout.
    238                 socket:sendto(err("Ack timeout"), host, port)
    239                 error("Timeout waiting for ACK")
    240             end
    241             --Okay, we've been acked in reasonable time.
    242             tid = tid + 1
    243             if done then success() end
    244             yield(true, true)
    245         end
    246     end)
    247 end
    248 
    249 function tftp:handle_WRQ(socket, host, port, sink)
    250     error"Not Implemented"
    251 end
    252 
    253 function tftp:listen(rrq_generator_callback, wrq_generator_callback, hosts, port, logfile)
    254 --[[--
    255     Listen for TFTP requests on UDP ```bind``:`port`` (0.0.0.0:69 by default)
    256     and get data from / send data to user-generated source/sink functions.
    257     Data is generated/received by functions returned by the the user-supplied
    258     ``rrq_generator_callback``/``wrq_generator_callback`` factory functions.
    259     For each resource requested, the generator function will be called
    260     with three arguments:
    261        - the requested resource as a C-style string (no embedded NUL chars)
    262        - the ip address of the peer, as a dotted-quad string ("1.2.3.4")
    263        - the port number of the peer
    264     It should return a source or sink function that will be called repeatedly
    265     until the data transfer is complete:
    266         (SOURCE) will be called once for each block of data : it takes a
    267          single argument of the requested data length in bytes
    268          and returns the next block of data. It must return as follows
    269                 `true, data` on success
    270                 `true, nil` on wouldblock but should retry next round,
    271                 `false` on finished
    272         (SINK) takes two arguments
    273             ``data`` to write
    274             ``done`` (truthy), whether all data has been received and backends can cleanup.
    275 
    276     The (SOURCE) model therefore supports both blocking and non-blocking behaviour.
    277     If the given function blocks, however, it will block the whole process as Lua
    278     is single threaded. That may or may not be acceptable depending on your needs.
    279     If the requested resource is invalid or other termination conditions are met,
    280     (SOURCE) and (SINK) functions should raise an error.
    281     @return This method never returns unless interrupted.
    282 ]]
    283 
    284     local function create_handler(callbacks, request, requestsocket, host, port)
    285          --[[ Given a parsed request, instantiate the generator function from the given callbacks,
    286             and create a new coroutine to be called when the state of the handler's
    287             new socket changes to available.
    288             On success, returns a table of the form:
    289             ```{
    290                 handler=coroutine to call,
    291                 socket= new socket on a random port on which all new communication will happen,
    292                 fd=socket.fd as above fd
    293                 host=remote host,
    294                 port = remote port,
    295                 request = the original parsed request, including accepted options, if any.
    296             }```
    297             On error, responds to the client with an ERROR packet, and returns nil.
    298         ]]
    299         local okay, generator, tsize = pcall(callbacks[request.opcode], request.filename, host, port)
    300         if not okay then
    301             requestsocket:sendto(self.ERROR(ERR_NOTFOUND), host, port)
    302             return nil
    303         else
    304             if request.options then
    305                 request.options.tsize = request.options.tsize and tostring(tsize)
    306                 for k, v in pairs(request.options) do
    307                     if not SUPPORTED_OPTIONS[k] then request.options[k] = nil end
    308                 end
    309             else
    310                 --RFC1350 requires WRQ requests to be responded to with a zero TID before transfer commences,
    311                 --but when responding to an options request, it is dropped.
    312                 if request.opcode == 'WRQ' then requestsocket:sendto(self.ACK(0), host, port) end
    313             end
    314             local handlersocket = UDPSocket()
    315 	    handlersocket:bind("*", 0)
    316             local handler = self['handle_' .. request.opcode](self, handlersocket, host, port, generator, request.options)
    317             return {
    318                 handler=handler,
    319                 socket=handlersocket,
    320                 fd=handlersocket.fd,
    321                 host=host,
    322                 port=port,
    323                 request=request,
    324                 wantread=false,
    325                 wantwrite=true,
    326             }
    327         end
    328     end
    329 
    330     local function accept(socket)
    331         --[[ Read an incoming request from ``socket``, parse, and ACK as appropriate.
    332             If the request is invalid, responds to the client with error and returns `nil`
    333             otherwise returns the parsed request.
    334         ]]
    335         local msg, host, port = socket:recvfrom()
    336         if msg ~= false then
    337             local okay, xRQ = pcall(self.parse_XRQ, msg)
    338             if not okay then
    339                 return nil
    340             else
    341                 return host, port, xRQ
    342             end
    343         end
    344     end
    345 
    346     local socket = UDPSocket()
    347     local user_generator_callbacks = {RRQ=rrq_generator_callback, WRQ=wrq_generator_callback}
    348     local port = port or TFTP_PORT
    349     local logfile = logfile or io.stderr
    350     --listen on all given addresses, default to localhost if not given
    351     for i, address in pairs((type(hosts) == 'table' and hosts) or (hosts ~= nil and{hosts}) or {'127.0.0.1'}) do
    352        local ok, err = socket:bind(address, port)
    353        if not ok then error(err .. " binding to " .. address .. ":" .. port) end
    354     end
    355 
    356     --[[The main event loop does two things:
    357         1. Accepts new connections.
    358         2. Handles events occurring on all sockets by dispatching to a handler coroutine.
    359         3. Removes finished requests from the queue and destroys the sockets.
    360     ]]
    361     local handlers = {[socket.fd]={fd=socket.fd, socket=socket, listener=true, wantread=true}}
    362     while true do
    363         ready_fds = poll(handlers)
    364         do
    365             local n = 0
    366             for _ in pairs(ready_fds) do
    367                 n = n + 1
    368             end
    369 --            log(('There are %d sockets ready'):format(n))
    370         end
    371         for fd, status in pairs(ready_fds) do
    372 --            pretty.dump(ready_fds)
    373 --            log(('There are %d sockets ready'):format(#ready_fds))
    374             ready = handlers[fd]
    375             if ready.listener and status.readable then
    376                 --we've got a listener and should accept a new connection
    377                 local host, port, request = accept(ready.socket)
    378                 if host ~= nil then
    379                     log(("accepted new %s request - %s:%s/%s"):format(request.opcode, host, port, request.filename))
    380                     local handler = create_handler(
    381                         user_generator_callbacks,
    382                         request,
    383                         ready.socket,
    384                         host,
    385                         port
    386                     )
    387                     if handler then handlers[handler.socket.fd] = handler end
    388                 end
    389             elseif (status.readable or status.writeable) and ready.handler then
    390             --We've received an event on a socket associated with an existing handler coroutine.
    391                 local co_state = coroutine.status(ready.handler)
    392                 local okay, wantread, wantwrite
    393                 if co_state ~= 'dead' then
    394                     if (ready.wantread and status.readable) or (ready.wantwrite and status.writeable) then
    395                         okay, wantread, wantwrite = coroutine.resume(ready.handler)
    396                         ready.wantread = wantread
    397                         ready.wantwrite = wantwrite
    398                     end
    399                 end
    400                 if (not okay) or co_state == 'dead' then
    401                     --- the handler is finished; cleanup
    402                     ready.socket:close()
    403                     handlers[ready.fd] = nil
    404                     ready.fd = nil
    405                     ready = nil
    406                 end
    407             end
    408         end
    409     end
    410 end
    411 
    412 
    413 --[[ RRQ/ZRQ read/write request packets
    414     https://tools.ietf.org/html/rfc1350
    415     2 bytes     string    1 byte     string   1 byte
    416     ------------------------------------------------
    417    | Opcode |  Filename  |   0  |    Mode    |   0  |
    418     ------------------------------------------------
    419            Figure 5-1: RRQ/WRQ packet
    420 ]]
    421 function tftp.RRQ(filename)
    422 --  RFC1350:"The mail mode is obsolete and should not be implemented or used."
    423 --  We don't support netascii, which leaves 'octet' mode only
    424     return table.concat({create_opcode(OP_RRQ), filename, '\0', "octet", '\0'}, '')
    425 end
    426 
    427 function tftp.parse_XRQ(request)
    428     local opcode = assert(parse_opcode(request), "Invalid opcode")
    429     assert(({RRQ=true, XRQ=true})[opcode], "Not an xRQ")
    430     assert(request:sub(#request) == '\0', "Invalid request: expected ASCII NUL terminated request")
    431 
    432     local cstrings = {}
    433     function zero_iter(s)
    434         local pos = 1
    435         return function()
    436             --This is ugly. Lua 5.2 handles embedded NUL bytes in string.gmatch,
    437             --but vanilla Lua5.1 doesn't match correctly and luajit can't seem to parse them
    438             for i=pos, #s do
    439                 if s:byte(i) == 0 then
    440                     local sub = s:sub(pos, i-1)
    441                     pos = i+1
    442                     return sub
    443                 end
    444             end
    445         end
    446     end
    447     for s in zero_iter(request:sub(3)) do
    448         cstrings[#cstrings+1] = s
    449     end
    450     assert(#cstrings >= 2)
    451     local filename = assert(is_netascii(cstrings[1]) and cstrings[1], "Requested filename must be netascii")
    452     local mode = assert(({netascii='netascii', octet='octet'})[cstrings[2]])
    453     local options
    454     if #cstrings > 2 then
    455         options = {}
    456         assert(#cstrings % 2 == 0)
    457         for i=3, #cstrings, 2 do
    458             --[[ RFC1782, and 3247 require case insensitive comparisons.
    459                 We normalize them to lowercase with the consequence that
    460                 duplicate keys are replaced which are forbidden by the standard anyway.
    461             ]]
    462             options[cstrings[i]:lower()] = cstrings[i+1]:lower()
    463         end
    464     end
    465 
    466     return {opcode=opcode, filename=filename, mode=mode, options=options}
    467 end
    468 
    469 --[[ ACK functions
    470      2 bytes     2 bytes
    471      ---------------------
    472     | Opcode |   Block #  |
    473      ---------------------
    474      Figure 5-3: ACK packet
    475 ]]
    476 function tftp.parse_ACK(ack)
    477     --get the sequence number from an ACK or raise a error if not valid
    478     assert(#ack == ACKSIZE, "invalid ack")
    479     assert(parse_opcode(ack) == 'ACK', "invalid ack")
    480 
    481     -- extract the low and high order bytes and convert to an integer
    482     local high, low = ack:byte(3, 4)
    483     return (high * 256) + low
    484 end
    485 
    486 --[[
    487       +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
    488       |  opc  |  opt1  | 0 | value1 | 0 |  optN  | 0 | valueN | 0 |
    489       +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
    490 ]]
    491 function tftp.OACK(options)
    492     local stropts = {}
    493     for k, v in pairs(options) do
    494         assert(is_netascii(k))
    495         stropts[#stropts+1] =  k .. '\0' .. v .. '\0'
    496     end
    497     return create_opcode(OP_OACK) .. table.concat(stropts, '')
    498 end
    499 
    500 function tftp.ACK(tid)
    501     return table.concat({create_opcode(OP_ACK), create_opcode(tid)}, '')
    502 end
    503 
    504 
    505 --[[ DATA functions
    506    2 bytes     2 bytes      n bytes
    507    ----------------------------------
    508   | Opcode |   Block #  |   Data     |
    509    ----------------------------------
    510         Figure 5-2: DATA packet
    511 ]]
    512 function tftp.DATA(data, tid)
    513     local opcode = create_opcode(OP_DATA)
    514     local block = create_opcode(tid)
    515     return table.concat({opcode, block, data}, '')
    516 end
    517 
    518 function tftp.parse_DATA(data)
    519     assert(#data <= 512, "tftp data packets must be 512 bytes or less")
    520     assert(parse_opcode(data) == OP_DATA, "Invalid opcode")
    521     return {tid=parse_opcode(data:sub(3, 4)), data=data:sub(5)}
    522 end
    523 
    524 --[[ ERROR Functions
    525     2 bytes     2 bytes      string    1 byte
    526     -----------------------------------------
    527     | Opcode |  ErrorCode |   ErrMsg   |   0  |
    528     -----------------------------------------
    529         Figure 5-4: ERROR packet
    530 ]]
    531 function tftp.ERROR(err)
    532     local defined_errors = {
    533         --https://tools.ietf.org/html/rfc1350#page-10
    534         [0] = type(err) == 'string' and err or "Not defined",
    535         "File not found.",
    536         "Access violation.",
    537         "Disk full or allocation exceeded.",
    538         "Illegal TFTP operation.",
    539         "Unknown transfer ID.",
    540         "File already exists.",
    541         "No such user.",
    542     }
    543 
    544     local errno = type(err) == 'string' and 0 or err
    545     return table.concat({
    546         create_opcode(OP_ERR),
    547         create_opcode(errno),
    548         defined_errors[errno],
    549         '\0'
    550     }, '')
    551 end
    552 
    553 function tftp.parse_ERROR(err)
    554     assert(parse_opcode(err) == OP_ERR)
    555     local error_code = parse_opcode(err:sub(3, 4))
    556     return {errcode=error_code, errmsg=err:sub(5, #err-1)}
    557 end
    558 
    559 return tftp