Home | History | Annotate | Download | only in layers
      1 ## This file is part of Scapy
      2 ## See http://www.secdev.org/projects/scapy for more informations
      3 ## Copyright (C) Philippe Biondi <phil (at] secdev.org>
      4 ## This program is published under a GPLv2 license
      5 
      6 """
      7 TFTP (Trivial File Transfer Protocol).
      8 """
      9 
     10 from __future__ import absolute_import
     11 import os,random
     12 from scapy.packet import *
     13 from scapy.fields import *
     14 from scapy.automaton import *
     15 from scapy.layers.inet import UDP, IP
     16 from scapy.modules.six.moves import range
     17 
     18 
     19 
     20 TFTP_operations = { 1:"RRQ",2:"WRQ",3:"DATA",4:"ACK",5:"ERROR",6:"OACK" }
     21 
     22 
     23 class TFTP(Packet):
     24     name = "TFTP opcode"
     25     fields_desc = [ ShortEnumField("op", 1, TFTP_operations), ]
     26     
     27 
     28 
     29 class TFTP_RRQ(Packet):
     30     name = "TFTP Read Request"
     31     fields_desc = [ StrNullField("filename", ""),
     32                     StrNullField("mode", "octet") ]
     33     def answers(self, other):
     34         return 0
     35     def mysummary(self):
     36         return self.sprintf("RRQ %filename%"),[UDP]
     37         
     38 
     39 class TFTP_WRQ(Packet):
     40     name = "TFTP Write Request"
     41     fields_desc = [ StrNullField("filename", ""),
     42                     StrNullField("mode", "octet") ]
     43     def answers(self, other):
     44         return 0
     45     def mysummary(self):
     46         return self.sprintf("WRQ %filename%"),[UDP]
     47 
     48 class TFTP_DATA(Packet):
     49     name = "TFTP Data"
     50     fields_desc = [ ShortField("block", 0) ]
     51     def answers(self, other):
     52         return  self.block == 1 and isinstance(other, TFTP_RRQ)
     53     def mysummary(self):
     54         return self.sprintf("DATA %block%"),[UDP]
     55 
     56 class TFTP_Option(Packet):
     57     fields_desc = [ StrNullField("oname",""),
     58                     StrNullField("value","") ]
     59     def extract_padding(self, pkt):
     60         return "",pkt
     61 
     62 class TFTP_Options(Packet):
     63     fields_desc = [ PacketListField("options", [], TFTP_Option, length_from=lambda x:None) ]
     64 
     65     
     66 class TFTP_ACK(Packet):
     67     name = "TFTP Ack"
     68     fields_desc = [ ShortField("block", 0) ]
     69     def answers(self, other):
     70         if isinstance(other, TFTP_DATA):
     71             return self.block == other.block
     72         elif isinstance(other, TFTP_RRQ) or isinstance(other, TFTP_WRQ) or isinstance(other, TFTP_OACK):
     73             return self.block == 0
     74         return 0
     75     def mysummary(self):
     76         return self.sprintf("ACK %block%"),[UDP]
     77 
     78 TFTP_Error_Codes = {  0: "Not defined",
     79                       1: "File not found",
     80                       2: "Access violation",
     81                       3: "Disk full or allocation exceeded",
     82                       4: "Illegal TFTP operation",
     83                       5: "Unknown transfer ID",
     84                       6: "File already exists",
     85                       7: "No such user",
     86                       8: "Terminate transfer due to option negotiation",
     87                       }
     88     
     89 class TFTP_ERROR(Packet):
     90     name = "TFTP Error"
     91     fields_desc = [ ShortEnumField("errorcode", 0, TFTP_Error_Codes),
     92                     StrNullField("errormsg", "")]
     93     def answers(self, other):
     94         return (isinstance(other, TFTP_DATA) or
     95                 isinstance(other, TFTP_RRQ) or
     96                 isinstance(other, TFTP_WRQ) or 
     97                 isinstance(other, TFTP_ACK))
     98     def mysummary(self):
     99         return self.sprintf("ERROR %errorcode%: %errormsg%"),[UDP]
    100 
    101 
    102 class TFTP_OACK(Packet):
    103     name = "TFTP Option Ack"
    104     fields_desc = [  ]
    105     def answers(self, other):
    106         return isinstance(other, TFTP_WRQ) or isinstance(other, TFTP_RRQ)
    107 
    108 
    109 bind_layers(UDP, TFTP, dport=69)
    110 bind_layers(TFTP, TFTP_RRQ, op=1)
    111 bind_layers(TFTP, TFTP_WRQ, op=2)
    112 bind_layers(TFTP, TFTP_DATA, op=3)
    113 bind_layers(TFTP, TFTP_ACK, op=4)
    114 bind_layers(TFTP, TFTP_ERROR, op=5)
    115 bind_layers(TFTP, TFTP_OACK, op=6)
    116 bind_layers(TFTP_RRQ, TFTP_Options)
    117 bind_layers(TFTP_WRQ, TFTP_Options)
    118 bind_layers(TFTP_OACK, TFTP_Options)
    119     
    120 
    121 class TFTP_read(Automaton):
    122     def parse_args(self, filename, server, sport = None, port=69, **kargs):
    123         Automaton.parse_args(self, **kargs)
    124         self.filename = filename
    125         self.server = server
    126         self.port = port
    127         self.sport = sport
    128 
    129 
    130     def master_filter(self, pkt):
    131         return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt
    132                  and pkt[UDP].dport == self.my_tid
    133                  and (self.server_tid is None or pkt[UDP].sport == self.server_tid) )
    134         
    135     # BEGIN
    136     @ATMT.state(initial=1)
    137     def BEGIN(self):
    138         self.blocksize=512
    139         self.my_tid = self.sport or RandShort()._fix()
    140         bind_bottom_up(UDP, TFTP, dport=self.my_tid)
    141         self.server_tid = None
    142         self.res = ""
    143 
    144         self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP()
    145         self.last_packet = self.l3/TFTP_RRQ(filename=self.filename, mode="octet")
    146         self.send(self.last_packet)
    147         self.awaiting=1
    148         
    149         raise self.WAITING()
    150         
    151     # WAITING
    152     @ATMT.state()
    153     def WAITING(self):
    154         pass
    155 
    156 
    157     @ATMT.receive_condition(WAITING)
    158     def receive_data(self, pkt):
    159         if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting:
    160             if self.server_tid is None:
    161                 self.server_tid = pkt[UDP].sport
    162                 self.l3[UDP].dport = self.server_tid
    163             raise self.RECEIVING(pkt)
    164 
    165     @ATMT.receive_condition(WAITING, prio=1)
    166     def receive_error(self, pkt):
    167         if TFTP_ERROR in pkt:
    168             raise self.ERROR(pkt)
    169     
    170         
    171     @ATMT.timeout(WAITING, 3)
    172     def timeout_waiting(self):
    173         raise self.WAITING()
    174     @ATMT.action(timeout_waiting)
    175     def retransmit_last_packet(self):
    176         self.send(self.last_packet)
    177 
    178     @ATMT.action(receive_data)
    179 #    @ATMT.action(receive_error)
    180     def send_ack(self):
    181         self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting)
    182         self.send(self.last_packet)
    183     
    184 
    185     # RECEIVED
    186     @ATMT.state()
    187     def RECEIVING(self, pkt):
    188         if conf.raw_layer in pkt:
    189             recvd = pkt[conf.raw_layer].load
    190         else:
    191             recvd = ""
    192         self.res += recvd
    193         self.awaiting += 1
    194         if len(recvd) == self.blocksize:
    195             raise self.WAITING()
    196         raise self.END()
    197 
    198     # ERROR
    199     @ATMT.state(error=1)
    200     def ERROR(self,pkt):
    201         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    202         return pkt[TFTP_ERROR].summary()
    203     
    204     #END
    205     @ATMT.state(final=1)
    206     def END(self):
    207         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    208         return self.res
    209 
    210 
    211 
    212 
    213 class TFTP_write(Automaton):
    214     def parse_args(self, filename, data, server, sport=None, port=69,**kargs):
    215         Automaton.parse_args(self, **kargs)
    216         self.filename = filename
    217         self.server = server
    218         self.port = port
    219         self.sport = sport
    220         self.blocksize = 512
    221         self.origdata = data
    222 
    223     def master_filter(self, pkt):
    224         return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt
    225                  and pkt[UDP].dport == self.my_tid
    226                  and (self.server_tid is None or pkt[UDP].sport == self.server_tid) )
    227         
    228 
    229     # BEGIN
    230     @ATMT.state(initial=1)
    231     def BEGIN(self):
    232         self.data = [self.origdata[i*self.blocksize:(i+1)*self.blocksize]
    233                      for i in range( len(self.origdata)/self.blocksize+1)]
    234         self.my_tid = self.sport or RandShort()._fix()
    235         bind_bottom_up(UDP, TFTP, dport=self.my_tid)
    236         self.server_tid = None
    237         
    238         self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP()
    239         self.last_packet = self.l3/TFTP_WRQ(filename=self.filename, mode="octet")
    240         self.send(self.last_packet)
    241         self.res = ""
    242         self.awaiting=0
    243 
    244         raise self.WAITING_ACK()
    245         
    246     # WAITING_ACK
    247     @ATMT.state()
    248     def WAITING_ACK(self):
    249         pass
    250 
    251     @ATMT.receive_condition(WAITING_ACK)    
    252     def received_ack(self,pkt):
    253         if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.awaiting:
    254             if self.server_tid is None:
    255                 self.server_tid = pkt[UDP].sport
    256                 self.l3[UDP].dport = self.server_tid
    257             raise self.SEND_DATA()
    258 
    259     @ATMT.receive_condition(WAITING_ACK)
    260     def received_error(self, pkt):
    261         if TFTP_ERROR in pkt:
    262             raise self.ERROR(pkt)
    263 
    264     @ATMT.timeout(WAITING_ACK, 3)
    265     def timeout_waiting(self):
    266         raise self.WAITING_ACK()
    267     @ATMT.action(timeout_waiting)
    268     def retransmit_last_packet(self):
    269         self.send(self.last_packet)
    270     
    271     # SEND_DATA
    272     @ATMT.state()
    273     def SEND_DATA(self):
    274         self.awaiting += 1
    275         self.last_packet = self.l3/TFTP_DATA(block=self.awaiting)/self.data.pop(0)
    276         self.send(self.last_packet)
    277         if self.data:
    278             raise self.WAITING_ACK()
    279         raise self.END()
    280     
    281 
    282     # ERROR
    283     @ATMT.state(error=1)
    284     def ERROR(self,pkt):
    285         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    286         return pkt[TFTP_ERROR].summary()
    287 
    288     # END
    289     @ATMT.state(final=1)
    290     def END(self):
    291         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    292 
    293 
    294 class TFTP_WRQ_server(Automaton):
    295 
    296     def parse_args(self, ip=None, sport=None, *args, **kargs):
    297         Automaton.parse_args(self, *args, **kargs)
    298         self.ip = ip
    299         self.sport = sport
    300 
    301     def master_filter(self, pkt):
    302         return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip)
    303 
    304     @ATMT.state(initial=1)
    305     def BEGIN(self):
    306         self.blksize=512
    307         self.blk=1
    308         self.filedata=""
    309         self.my_tid = self.sport or random.randint(10000,65500)
    310         bind_bottom_up(UDP, TFTP, dport=self.my_tid)
    311 
    312     @ATMT.receive_condition(BEGIN)
    313     def receive_WRQ(self,pkt):
    314         if TFTP_WRQ in pkt:
    315             raise self.WAIT_DATA().action_parameters(pkt)
    316         
    317     @ATMT.action(receive_WRQ)
    318     def ack_WRQ(self, pkt):
    319         ip = pkt[IP]
    320         self.ip = ip.dst
    321         self.dst = ip.src
    322         self.filename = pkt[TFTP_WRQ].filename
    323         options = pkt.getlayer(TFTP_Options)
    324         self.l3 = IP(src=ip.dst, dst=ip.src)/UDP(sport=self.my_tid, dport=pkt.sport)/TFTP()
    325         if options is None:
    326             self.last_packet = self.l3/TFTP_ACK(block=0)
    327             self.send(self.last_packet)
    328         else:
    329             opt = [x for x in options.options if x.oname.upper() == "BLKSIZE"]
    330             if opt:
    331                 self.blksize = int(opt[0].value)
    332                 self.debug(2,"Negotiated new blksize at %i" % self.blksize)
    333             self.last_packet = self.l3/TFTP_OACK()/TFTP_Options(options=opt)
    334             self.send(self.last_packet)
    335 
    336     @ATMT.state()
    337     def WAIT_DATA(self):
    338         pass
    339 
    340     @ATMT.timeout(WAIT_DATA, 1)
    341     def resend_ack(self):
    342         self.send(self.last_packet)
    343         raise self.WAIT_DATA()
    344         
    345     @ATMT.receive_condition(WAIT_DATA)
    346     def receive_data(self, pkt):
    347         if TFTP_DATA in pkt:
    348             data = pkt[TFTP_DATA]
    349             if data.block == self.blk:
    350                 raise self.DATA(data)
    351 
    352     @ATMT.action(receive_data)
    353     def ack_data(self):
    354         self.last_packet = self.l3/TFTP_ACK(block = self.blk)
    355         self.send(self.last_packet)
    356 
    357     @ATMT.state()
    358     def DATA(self, data):
    359         self.filedata += data.load
    360         if len(data.load) < self.blksize:
    361             raise self.END()
    362         self.blk += 1
    363         raise self.WAIT_DATA()
    364 
    365     @ATMT.state(final=1)
    366     def END(self):
    367         return self.filename,self.filedata
    368         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    369         
    370 
    371 class TFTP_RRQ_server(Automaton):
    372     def parse_args(self, store=None, joker=None, dir=None, ip=None, sport=None, serve_one=False, **kargs):
    373         Automaton.parse_args(self,**kargs)
    374         if store is None:
    375             store = {}
    376         if dir is not None:
    377             self.dir = os.path.join(os.path.abspath(dir),"")
    378         else:
    379             self.dir = None
    380         self.store = store
    381         self.joker = joker
    382         self.ip = ip
    383         self.sport = sport
    384         self.serve_one = serve_one
    385         self.my_tid = self.sport or random.randint(10000,65500)
    386         bind_bottom_up(UDP, TFTP, dport=self.my_tid)
    387         
    388     def master_filter(self, pkt):
    389         return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip)
    390 
    391     @ATMT.state(initial=1)
    392     def WAIT_RRQ(self):
    393         self.blksize=512
    394         self.blk=0
    395 
    396     @ATMT.receive_condition(WAIT_RRQ)
    397     def receive_rrq(self, pkt):
    398         if TFTP_RRQ in pkt:
    399             raise self.RECEIVED_RRQ(pkt)
    400 
    401 
    402     @ATMT.state()
    403     def RECEIVED_RRQ(self, pkt):
    404         ip = pkt[IP]
    405         options = pkt[TFTP_Options]
    406         self.l3 = IP(src=ip.dst, dst=ip.src)/UDP(sport=self.my_tid, dport=ip.sport)/TFTP()
    407         self.filename = pkt[TFTP_RRQ].filename
    408         self.blk=1
    409         self.data = None
    410         if self.filename in self.store:
    411             self.data = self.store[self.filename]
    412         elif self.dir is not None:
    413             fn = os.path.abspath(os.path.join(self.dir, self.filename))
    414             if fn.startswith(self.dir): # Check we're still in the server's directory
    415                 try:
    416                     self.data=open(fn).read()
    417                 except IOError:
    418                     pass
    419         if self.data is None:
    420             self.data = self.joker
    421 
    422         if options:
    423             opt = [x for x in options.options if x.oname.upper() == "BLKSIZE"]
    424             if opt:
    425                 self.blksize = int(opt[0].value)
    426                 self.debug(2,"Negotiated new blksize at %i" % self.blksize)
    427             self.last_packet = self.l3/TFTP_OACK()/TFTP_Options(options=opt)
    428             self.send(self.last_packet)
    429                 
    430 
    431             
    432 
    433     @ATMT.condition(RECEIVED_RRQ)
    434     def file_in_store(self):
    435         if self.data is not None:
    436             self.blknb = len(self.data)/self.blksize+1
    437             raise self.SEND_FILE()
    438 
    439     @ATMT.condition(RECEIVED_RRQ)
    440     def file_not_found(self):
    441         if self.data is None:
    442             raise self.WAIT_RRQ()
    443     @ATMT.action(file_not_found)
    444     def send_error(self):
    445         self.send(self.l3/TFTP_ERROR(errorcode=1, errormsg=TFTP_Error_Codes[1]))
    446 
    447     @ATMT.state()
    448     def SEND_FILE(self):
    449         self.send(self.l3/TFTP_DATA(block=self.blk)/self.data[(self.blk-1)*self.blksize:self.blk*self.blksize])
    450         
    451     @ATMT.timeout(SEND_FILE, 3)
    452     def timeout_waiting_ack(self):
    453         raise self.SEND_FILE()
    454             
    455     @ATMT.receive_condition(SEND_FILE)
    456     def received_ack(self, pkt):
    457         if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.blk:
    458             raise self.RECEIVED_ACK()
    459     @ATMT.state()
    460     def RECEIVED_ACK(self):
    461         self.blk += 1
    462 
    463     @ATMT.condition(RECEIVED_ACK)
    464     def no_more_data(self):
    465         if self.blk > self.blknb:
    466             if self.serve_one:
    467                 raise self.END()
    468             raise self.WAIT_RRQ()
    469     @ATMT.condition(RECEIVED_ACK, prio=2)
    470     def data_remaining(self):
    471         raise self.SEND_FILE()
    472 
    473     @ATMT.state(final=1)
    474     def END(self):
    475         split_bottom_up(UDP, TFTP, dport=self.my_tid)
    476     
    477 
    478         
    479 
    480