liminix

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

tftp.lua (21012B)


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