1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7TFTP (Trivial File Transfer Protocol).
8"""
9
10from __future__ import absolute_import
11import os,random
12from scapy.packet import *
13from scapy.fields import *
14from scapy.automaton import *
15from scapy.layers.inet import UDP, IP
16from scapy.modules.six.moves import range
17
18
19
20TFTP_operations = { 1:"RRQ",2:"WRQ",3:"DATA",4:"ACK",5:"ERROR",6:"OACK" }
21
22
23class TFTP(Packet):
24    name = "TFTP opcode"
25    fields_desc = [ ShortEnumField("op", 1, TFTP_operations), ]
26
27
28
29class 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
39class 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
48class 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
56class TFTP_Option(Packet):
57    fields_desc = [ StrNullField("oname",""),
58                    StrNullField("value","") ]
59    def extract_padding(self, pkt):
60        return "",pkt
61
62class TFTP_Options(Packet):
63    fields_desc = [ PacketListField("options", [], TFTP_Option, length_from=lambda x:None) ]
64
65
66class 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
78TFTP_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
89class 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
102class 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
109bind_layers(UDP, TFTP, dport=69)
110bind_layers(TFTP, TFTP_RRQ, op=1)
111bind_layers(TFTP, TFTP_WRQ, op=2)
112bind_layers(TFTP, TFTP_DATA, op=3)
113bind_layers(TFTP, TFTP_ACK, op=4)
114bind_layers(TFTP, TFTP_ERROR, op=5)
115bind_layers(TFTP, TFTP_OACK, op=6)
116bind_layers(TFTP_RRQ, TFTP_Options)
117bind_layers(TFTP_WRQ, TFTP_Options)
118bind_layers(TFTP_OACK, TFTP_Options)
119
120
121class 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
213class 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
294class 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
371class 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