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