blob: d7c25ca24694857c6935aa2d966e006ba9e98270 [file] [log] [blame]
Norman James8b2b7222015-10-08 07:01:38 -05001"""This module implements the packet types of TFTP itself, and the
2corresponding encode and decode methods for them."""
3
4import struct
5from TftpShared import *
6
7class TftpSession(object):
8 """This class is the base class for the tftp client and server. Any shared
9 code should be in this class."""
10 # FIXME: do we need this anymore?
11 pass
12
13class TftpPacketWithOptions(object):
14 """This class exists to permit some TftpPacket subclasses to share code
15 regarding options handling. It does not inherit from TftpPacket, as the
16 goal is just to share code here, and not cause diamond inheritance."""
17
18 def __init__(self):
19 self.options = {}
20
21 def setoptions(self, options):
22 log.debug("in TftpPacketWithOptions.setoptions")
23 log.debug("options: %s", str(options))
24 myoptions = {}
25 for key in options:
26 newkey = str(key)
27 myoptions[newkey] = str(options[key])
28 log.debug("populated myoptions with %s = %s",
29 newkey, myoptions[newkey])
30
31 log.debug("setting options hash to: %s", str(myoptions))
32 self._options = myoptions
33
34 def getoptions(self):
35 log.debug("in TftpPacketWithOptions.getoptions")
36 return self._options
37
38 # Set up getter and setter on options to ensure that they are the proper
39 # type. They should always be strings, but we don't need to force the
40 # client to necessarily enter strings if we can avoid it.
41 options = property(getoptions, setoptions)
42
43 def decode_options(self, buffer):
44 """This method decodes the section of the buffer that contains an
45 unknown number of options. It returns a dictionary of option names and
46 values."""
47 format = "!"
48 options = {}
49
50 log.debug("decode_options: buffer is: %s", repr(buffer))
51 log.debug("size of buffer is %d bytes", len(buffer))
52 if len(buffer) == 0:
53 log.debug("size of buffer is zero, returning empty hash")
54 return {}
55
56 # Count the nulls in the buffer. Each one terminates a string.
57 log.debug("about to iterate options buffer counting nulls")
58 length = 0
59 for c in buffer:
60 if ord(c) == 0:
61 log.debug("found a null at length %d", length)
62 if length > 0:
63 format += "%dsx" % length
64 length = -1
65 else:
66 raise TftpException, "Invalid options in buffer"
67 length += 1
68
69 log.debug("about to unpack, format is: %s", format)
70 mystruct = struct.unpack(format, buffer)
71
72 tftpassert(len(mystruct) % 2 == 0,
73 "packet with odd number of option/value pairs")
74
75 for i in range(0, len(mystruct), 2):
76 log.debug("setting option %s to %s", mystruct[i], mystruct[i+1])
77 options[mystruct[i]] = mystruct[i+1]
78
79 return options
80
81class TftpPacket(object):
82 """This class is the parent class of all tftp packet classes. It is an
83 abstract class, providing an interface, and should not be instantiated
84 directly."""
85 def __init__(self):
86 self.opcode = 0
87 self.buffer = None
88
89 def encode(self):
90 """The encode method of a TftpPacket takes keyword arguments specific
91 to the type of packet, and packs an appropriate buffer in network-byte
92 order suitable for sending over the wire.
93
94 This is an abstract method."""
95 raise NotImplementedError, "Abstract method"
96
97 def decode(self):
98 """The decode method of a TftpPacket takes a buffer off of the wire in
99 network-byte order, and decodes it, populating internal properties as
100 appropriate. This can only be done once the first 2-byte opcode has
101 already been decoded, but the data section does include the entire
102 datagram.
103
104 This is an abstract method."""
105 raise NotImplementedError, "Abstract method"
106
107class TftpPacketInitial(TftpPacket, TftpPacketWithOptions):
108 """This class is a common parent class for the RRQ and WRQ packets, as
109 they share quite a bit of code."""
110 def __init__(self):
111 TftpPacket.__init__(self)
112 TftpPacketWithOptions.__init__(self)
113 self.filename = None
114 self.mode = None
115
116 def encode(self):
117 """Encode the packet's buffer from the instance variables."""
118 tftpassert(self.filename, "filename required in initial packet")
119 tftpassert(self.mode, "mode required in initial packet")
120
121 ptype = None
122 if self.opcode == 1: ptype = "RRQ"
123 else: ptype = "WRQ"
124 log.debug("Encoding %s packet, filename = %s, mode = %s",
125 ptype, self.filename, self.mode)
126 for key in self.options:
127 log.debug(" Option %s = %s", key, self.options[key])
128
129 format = "!H"
130 format += "%dsx" % len(self.filename)
131 if self.mode == "octet":
132 format += "5sx"
133 else:
134 raise AssertionError, "Unsupported mode: %s" % mode
135 # Add options.
136 options_list = []
137 if self.options.keys() > 0:
138 log.debug("there are options to encode")
139 for key in self.options:
140 # Populate the option name
141 format += "%dsx" % len(key)
142 options_list.append(key)
143 # Populate the option value
144 format += "%dsx" % len(str(self.options[key]))
145 options_list.append(str(self.options[key]))
146
147 log.debug("format is %s", format)
148 log.debug("options_list is %s", options_list)
149 log.debug("size of struct is %d", struct.calcsize(format))
150
151 self.buffer = struct.pack(format,
152 self.opcode,
153 self.filename,
154 self.mode,
155 *options_list)
156
157 log.debug("buffer is %s", repr(self.buffer))
158 return self
159
160 def decode(self):
161 tftpassert(self.buffer, "Can't decode, buffer is empty")
162
163 # FIXME - this shares a lot of code with decode_options
164 nulls = 0
165 format = ""
166 nulls = length = tlength = 0
167 log.debug("in decode: about to iterate buffer counting nulls")
168 subbuf = self.buffer[2:]
169 for c in subbuf:
170 if ord(c) == 0:
171 nulls += 1
172 log.debug("found a null at length %d, now have %d", length, nulls)
173 format += "%dsx" % length
174 length = -1
175 # At 2 nulls, we want to mark that position for decoding.
176 if nulls == 2:
177 break
178 length += 1
179 tlength += 1
180
181 log.debug("hopefully found end of mode at length %d", tlength)
182 # length should now be the end of the mode.
183 tftpassert(nulls == 2, "malformed packet")
184 shortbuf = subbuf[:tlength+1]
185 log.debug("about to unpack buffer with format: %s", format)
186 log.debug("unpacking buffer: %s", repr(shortbuf))
187 mystruct = struct.unpack(format, shortbuf)
188
189 tftpassert(len(mystruct) == 2, "malformed packet")
190 self.filename = mystruct[0]
191 self.mode = mystruct[1].lower() # force lc - bug 17
192 log.debug("set filename to %s", self.filename)
193 log.debug("set mode to %s", self.mode)
194
195 self.options = self.decode_options(subbuf[tlength+1:])
196 return self
197
198class TftpPacketRRQ(TftpPacketInitial):
199 """
200::
201
202 2 bytes string 1 byte string 1 byte
203 -----------------------------------------------
204 RRQ/ | 01/02 | Filename | 0 | Mode | 0 |
205 WRQ -----------------------------------------------
206 """
207 def __init__(self):
208 TftpPacketInitial.__init__(self)
209 self.opcode = 1
210
211 def __str__(self):
212 s = 'RRQ packet: filename = %s' % self.filename
213 s += ' mode = %s' % self.mode
214 if self.options:
215 s += '\n options = %s' % self.options
216 return s
217
218class TftpPacketWRQ(TftpPacketInitial):
219 """
220::
221
222 2 bytes string 1 byte string 1 byte
223 -----------------------------------------------
224 RRQ/ | 01/02 | Filename | 0 | Mode | 0 |
225 WRQ -----------------------------------------------
226 """
227 def __init__(self):
228 TftpPacketInitial.__init__(self)
229 self.opcode = 2
230
231 def __str__(self):
232 s = 'WRQ packet: filename = %s' % self.filename
233 s += ' mode = %s' % self.mode
234 if self.options:
235 s += '\n options = %s' % self.options
236 return s
237
238class TftpPacketDAT(TftpPacket):
239 """
240::
241
242 2 bytes 2 bytes n bytes
243 ---------------------------------
244 DATA | 03 | Block # | Data |
245 ---------------------------------
246 """
247 def __init__(self):
248 TftpPacket.__init__(self)
249 self.opcode = 3
250 self.blocknumber = 0
251 self.data = None
252
253 def __str__(self):
254 s = 'DAT packet: block %s' % self.blocknumber
255 if self.data:
256 s += '\n data: %d bytes' % len(self.data)
257 return s
258
259 def encode(self):
260 """Encode the DAT packet. This method populates self.buffer, and
261 returns self for easy method chaining."""
262 if len(self.data) == 0:
263 log.debug("Encoding an empty DAT packet")
264 format = "!HH%ds" % len(self.data)
265 self.buffer = struct.pack(format,
266 self.opcode,
267 self.blocknumber,
268 self.data)
269 return self
270
271 def decode(self):
272 """Decode self.buffer into instance variables. It returns self for
273 easy method chaining."""
274 # We know the first 2 bytes are the opcode. The second two are the
275 # block number.
276 (self.blocknumber,) = struct.unpack("!H", self.buffer[2:4])
277 log.debug("decoding DAT packet, block number %d", self.blocknumber)
278 log.debug("should be %d bytes in the packet total", len(self.buffer))
279 # Everything else is data.
280 self.data = self.buffer[4:]
281 log.debug("found %d bytes of data", len(self.data))
282 return self
283
284class TftpPacketACK(TftpPacket):
285 """
286::
287
288 2 bytes 2 bytes
289 -------------------
290 ACK | 04 | Block # |
291 --------------------
292 """
293 def __init__(self):
294 TftpPacket.__init__(self)
295 self.opcode = 4
296 self.blocknumber = 0
297
298 def __str__(self):
299 return 'ACK packet: block %d' % self.blocknumber
300
301 def encode(self):
302 log.debug("encoding ACK: opcode = %d, block = %d",
303 self.opcode, self.blocknumber)
304 self.buffer = struct.pack("!HH", self.opcode, self.blocknumber)
305 return self
306
307 def decode(self):
308 if len(self.buffer) > 4:
309 log.debug("detected TFTP ACK but request is too large, will truncate")
310 log.debug("buffer was: %s", repr(self.buffer))
311 self.buffer = self.buffer[0:4]
312 self.opcode, self.blocknumber = struct.unpack("!HH", self.buffer)
313 log.debug("decoded ACK packet: opcode = %d, block = %d",
314 self.opcode, self.blocknumber)
315 return self
316
317class TftpPacketERR(TftpPacket):
318 """
319::
320
321 2 bytes 2 bytes string 1 byte
322 ----------------------------------------
323 ERROR | 05 | ErrorCode | ErrMsg | 0 |
324 ----------------------------------------
325
326 Error Codes
327
328 Value Meaning
329
330 0 Not defined, see error message (if any).
331 1 File not found.
332 2 Access violation.
333 3 Disk full or allocation exceeded.
334 4 Illegal TFTP operation.
335 5 Unknown transfer ID.
336 6 File already exists.
337 7 No such user.
338 8 Failed to negotiate options
339 """
340 def __init__(self):
341 TftpPacket.__init__(self)
342 self.opcode = 5
343 self.errorcode = 0
344 # FIXME: We don't encode the errmsg...
345 self.errmsg = None
346 # FIXME - integrate in TftpErrors references?
347 self.errmsgs = {
348 1: "File not found",
349 2: "Access violation",
350 3: "Disk full or allocation exceeded",
351 4: "Illegal TFTP operation",
352 5: "Unknown transfer ID",
353 6: "File already exists",
354 7: "No such user",
355 8: "Failed to negotiate options"
356 }
357
358 def __str__(self):
359 s = 'ERR packet: errorcode = %d' % self.errorcode
360 s += '\n msg = %s' % self.errmsgs.get(self.errorcode, '')
361 return s
362
363 def encode(self):
364 """Encode the DAT packet based on instance variables, populating
365 self.buffer, returning self."""
366 format = "!HH%dsx" % len(self.errmsgs[self.errorcode])
367 log.debug("encoding ERR packet with format %s", format)
368 self.buffer = struct.pack(format,
369 self.opcode,
370 self.errorcode,
371 self.errmsgs[self.errorcode])
372 return self
373
374 def decode(self):
375 "Decode self.buffer, populating instance variables and return self."
376 buflen = len(self.buffer)
377 tftpassert(buflen >= 4, "malformed ERR packet, too short")
378 log.debug("Decoding ERR packet, length %s bytes", buflen)
379 if buflen == 4:
380 log.debug("Allowing this affront to the RFC of a 4-byte packet")
381 format = "!HH"
382 log.debug("Decoding ERR packet with format: %s", format)
383 self.opcode, self.errorcode = struct.unpack(format,
384 self.buffer)
385 else:
386 log.debug("Good ERR packet > 4 bytes")
387 format = "!HH%dsx" % (len(self.buffer) - 5)
388 log.debug("Decoding ERR packet with format: %s", format)
389 self.opcode, self.errorcode, self.errmsg = struct.unpack(format,
390 self.buffer)
391 log.error("ERR packet - errorcode: %d, message: %s"
392 % (self.errorcode, self.errmsg))
393 return self
394
395class TftpPacketOACK(TftpPacket, TftpPacketWithOptions):
396 """
397::
398
399 +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
400 | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 |
401 +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+
402 """
403 def __init__(self):
404 TftpPacket.__init__(self)
405 TftpPacketWithOptions.__init__(self)
406 self.opcode = 6
407
408 def __str__(self):
409 return 'OACK packet:\n options = %s' % self.options
410
411 def encode(self):
412 format = "!H" # opcode
413 options_list = []
414 log.debug("in TftpPacketOACK.encode")
415 for key in self.options:
416 log.debug("looping on option key %s", key)
417 log.debug("value is %s", self.options[key])
418 format += "%dsx" % len(key)
419 format += "%dsx" % len(self.options[key])
420 options_list.append(key)
421 options_list.append(self.options[key])
422 self.buffer = struct.pack(format, self.opcode, *options_list)
423 return self
424
425 def decode(self):
426 self.options = self.decode_options(self.buffer[2:])
427 return self
428
429 def match_options(self, options):
430 """This method takes a set of options, and tries to match them with
431 its own. It can accept some changes in those options from the server as
432 part of a negotiation. Changed or unchanged, it will return a dict of
433 the options so that the session can update itself to the negotiated
434 options."""
435 for name in self.options:
436 if options.has_key(name):
437 if name == 'blksize':
438 # We can accept anything between the min and max values.
439 size = self.options[name]
440 if size >= MIN_BLKSIZE and size <= MAX_BLKSIZE:
441 log.debug("negotiated blksize of %d bytes", size)
442 options[blksize] = size
443 else:
444 raise TftpException, "Unsupported option: %s" % name
445 return True