# frozen_string_literal: true # This file is part of PacketGen # See https://github.com/sdaubert/packetgen for more informations # Copyright (C) 2016 Sylvain Daubert # This program is published under MIT license. module PacketGen module Header # A TFTP (Trivial File Transfer Protocol, # {https://tools.ietf.org/html/rfc1350 RFC 1350}) header consists of: # * a {#opcode} ({Types::Int16Enum}), # * and a body. Its content depends on opcode. # # Specialized subclasses exists to handle {TFTP::RRQ Read Request}, # {TFTP::WRQ Write Request}, {TFTP::DATA DATA}, {TFTP::ACK ACK} and # {TFTP::ERROR ERROR} packets. # # == Create a TFTP header # # standalone # tftp = PacketGen::Header::TFTP.new # # in a packet # pkt = PacketGen.gen('IP').add('UDP').add('TFTP') # # access to TFTP header # pkt.tftp # => PacketGen::Header::TFTP # # == TFTP attributes # tftp.opcode = 'RRQ' # tftp.opcode = 1 # tftp.body.read 'this is a body' # # == TFTP parsing # When parsing, only first packet (read or write request) should be decoded # as TFTP packet, as others uses custom UDP ports. # # So, to decode subsequent TFTP packets, a method {#decode!} is provided # for this purpose. This method takes a single array argument. This array # should contain all subsequent TFTP packets (others packet types may also # be included in this array: they won't be modified). +#decode!+ will modify # array in-place by replacing UDP packets by TFTP ones (if decoded as TFTP # packets): # # packets is an array of packets: TFTP::RRQ, UDP (should be TFTP::DATA), UDP (not a TFTP packet) and UDP (TFTP::ACK) # packets.map { |pkt| pkt.headers.last.class.to_s }.join(',') # => TFTP::RRQ,UDP,UDP,UDP # # Here, decoding TFTP packets # packets[0].tftp.decode!(packets[1..-1]) # packets.map { |pkt| pkt.headers.last.class.to_s }.join(',') # => TFTP::RRQ,TFTP::DATA,UDP,TFTP::ACK # @author Sylvain Daubert # @since 2.3.0 class TFTP < Base # Known opcodes OPCODES = { 'RRQ' => 1, 'WRQ' => 2, 'DATA' => 3, 'ACK' => 4, 'Error' => 5 }.freeze # @!attribute opcode # 16-bit operation code # @return [Integer] define_field :opcode, Types::Int16Enum, enum: OPCODES # @!attribute body # @return [String] define_field :body, Types::String def initialize(options={}) type = protocol_name.sub(/^.*::/, '') opcode = OPCODES[type] if (self.class != TFTP) && !opcode.nil? super({ opcode: opcode }.merge(options)) else super end end # @private alias old_read read # Populate object from binary string # @param [String] str # @return [TFTP] def read(str) if self.instance_of? TFTP super if OPCODES.value? opcode TFTP.const_get(human_opcode).new.read str else self end else old_read str end end # Decode subsequent TFTP packets to this one. Packets are modified # in place in +ary+. # @param [Array] ary # @return [void] def decode!(ary) client_tid = packet.udp.sport server_tid = nil ary.each do |pkt| if server_tid.nil? next unless pkt.is?('UDP') && (pkt.udp.dport == client_tid) server_tid = pkt.udp.sport else next unless pkt.is?('UDP') tids = [server_tid, client_tid] ports = [pkt.udp.sport, pkt.udp.dport] next unless (tids - ports).empty? end tftp = Packet.parse(pkt.body, first_header: 'TFTP') udp_dport = pkt.udp.dport pkt.encapsulate tftp # need to fix it as #encapsulate force it to 69 pkt.udp.dport = udp_dport end end # Get human readable opcode # @return [String] def human_opcode self[:opcode].to_human end # Callback called when a TFTP header is added to a packet # Here, add +#tftp+ method as a shortcut to existing # +#tftp(rrq|wrq|data|ack|error)+. # @param [Packet] packet # @return [void] def added_to_packet(packet) return if packet.respond_to? :tftp packet.instance_eval("def tftp(arg=nil); header(#{self.class}, arg); end") end # TFTP Read Request header class RRQ < TFTP remove_field :body # @!attribute filename # Filename to access # @return [String] define_field :filename, Types::CString # @!attribute mode # Mode used. Should be +netascii+, +octet+ or +mail+ # @return [String] define_field :mode, Types::CString end # TFTP Write Request header class WRQ < RRQ; end # TFTP DATA header class DATA < TFTP # @!attribute block_num # 16-bit block number # @return [Integer] define_field_before :body, :block_num, Types::Int16 end # TFTP ACK header class ACK < TFTP remove_field :body # @!attribute block_num # 16-bit block number # @return [Integer] define_field :block_num, Types::Int16 end # TFTP ERROR header class ERROR < TFTP remove_field :body # @!attribute error_code # 16-bit error code # @return [Integer] define_field :error_code, Types::Int16 # @!attribute error_msg # Error message # @return [String] define_field :error_msg, Types::CString alias error_message error_msg end end UDP.bind TFTP, dport: 69 end end