diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/bin/tftpy_client.py b/bin/tftpy_client.py old mode 100644 new mode 100755 index 961fb78..ba9da85 --- a/bin/tftpy_client.py +++ b/bin/tftpy_client.py @@ -34,6 +34,11 @@ def main(): action='store_true', default=False, help="downgrade logging from info to warning") + parser.add_option('-t', + '--tsize', + action='store_true', + default=False, + help="ask client to send tsize option in download") options, args = parser.parse_args() if not options.host or not options.filename: sys.stderr.write("Both the --host and --filename options " @@ -55,8 +60,11 @@ def main(): self.progress = 0 self.out = out def progresshook(self, pkt): - self.progress += len(pkt.data) - self.out("Downloaded %d bytes" % self.progress) + if isinstance(pkt, tftpy.TftpPacketDAT): + self.progress += len(pkt.data) + self.out("Downloaded %d bytes" % self.progress) + elif isinstance(pkt, tftpy.TftpPacketOACK): + self.out("Received OACK, options are: %s" % pkt.options) if options.debug: tftpy.setLogLevel(logging.DEBUG) @@ -70,6 +78,8 @@ def main(): tftp_options = {} if options.blocksize: tftp_options['blksize'] = int(options.blocksize) + if options.tsize: + tftp_options['tsize'] = 1 tclient = tftpy.TftpClient(options.host, int(options.port), diff --git a/doc/rfc2349.txt b/doc/rfc2349.txt new file mode 100644 index 0000000..31abb3e --- /dev/null +++ b/doc/rfc2349.txt @@ -0,0 +1,283 @@ + + + + + + +Network Working Group G. Malkin +Request for Commments: 2349 Bay Networks +Updates: 1350 A. Harkin +Obsoletes: 1784 Hewlett Packard Co. +Category: Standards Track May 1998 + + + TFTP Timeout Interval and Transfer Size Options + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + The Trivial File Transfer Protocol [1] is a simple, lock-step, file + transfer protocol which allows a client to get or put a file onto a + remote host. + + This document describes two TFTP options. The first allows the client + and server to negotiate the Timeout Interval. The second allows the + side receiving the file to determine the ultimate size of the + transfer before it begins. The TFTP Option Extension mechanism is + described in [2]. + +Timeout Interval Option Specification + + The TFTP Read Request or Write Request packet is modified to include + the timeout option as follows: + + +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + | opc |filename| 0 | mode | 0 | timeout| 0 | #secs | 0 | + +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + + opc + The opcode field contains either a 1, for Read Requests, or 2, + for Write Requests, as defined in [1]. + + + + + + +Malkin & Harkin Standards Track [Page 1] + +RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 + + + filename + The name of the file to be read or written, as defined in [1]. + This is a NULL-terminated field. + + mode + The mode of the file transfer: "netascii", "octet", or "mail", + as defined in [1]. This is a NULL-terminated field. + + timeout + The Timeout Interval option, "timeout" (case in-sensitive). + This is a NULL-terminated field. + + #secs + The number of seconds to wait before retransmitting, specified + in ASCII. Valid values range between "1" and "255" seconds, + inclusive. This is a NULL-terminated field. + + For example: + + +-------+--------+---+--------+---+--------+---+-------+---+ + | 1 | foobar | 0 | octet | 0 | timeout| 0 | 1 | 0 | + +-------+--------+---+--------+---+--------+---+-------+---+ + + is a Read Request, for the file named "foobar", in octet (binary) + transfer mode, with a timeout interval of 1 second. + + If the server is willing to accept the timeout option, it sends an + Option Acknowledgment (OACK) to the client. The specified timeout + value must match the value specified by the client. + +Transfer Size Option Specification + + The TFTP Read Request or Write Request packet is modified to include + the tsize option as follows: + + +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + | opc |filename| 0 | mode | 0 | tsize | 0 | size | 0 | + +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ + + opc + The opcode field contains either a 1, for Read Requests, or 2, + for Write Requests, as defined in [1]. + + filename + The name of the file to be read or written, as defined in [1]. + This is a NULL-terminated field. + + + + + +Malkin & Harkin Standards Track [Page 2] + +RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 + + + mode + The mode of the file transfer: "netascii", "octet", or "mail", + as defined in [1]. This is a NULL-terminated field. + + tsize + The Transfer Size option, "tsize" (case in-sensitive). This is + a NULL-terminated field. + + size + The size of the file to be transfered. This is a NULL- + terminated field. + + For example: + + +-------+--------+---+--------+---+--------+---+--------+---+ + | 2 | foobar | 0 | octet | 0 | tsize | 0 | 673312 | 0 | + +-------+--------+---+--------+---+--------+---+--------+---+ + + is a Write Request, with the 673312-octet file named "foobar", in + octet (binary) transfer mode. + + In Read Request packets, a size of "0" is specified in the request + and the size of the file, in octets, is returned in the OACK. If the + file is too large for the client to handle, it may abort the transfer + with an Error packet (error code 3). In Write Request packets, the + size of the file, in octets, is specified in the request and echoed + back in the OACK. If the file is too large for the server to handle, + it may abort the transfer with an Error packet (error code 3). + +Security Considerations + + The basic TFTP protocol has no security mechanism. This is why it + has no rename, delete, or file overwrite capabilities. This document + does not add any security to TFTP; however, the specified extensions + do not add any additional security risks. + +References + + [1] Sollins, K., "The TFTP Protocol (Revision 2)", STD 33, RFC 1350, + October 92. + + [2] Malkin, G., and A. Harkin, "TFTP Option Extension", RFC 2347, + May 1998. + + + + + + + + +Malkin & Harkin Standards Track [Page 3] + +RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 + + +Authors' Addresses + + Gary Scott Malkin + Bay Networks + 8 Federal Street + Billerica, MA 01821 + + Phone: (978) 916-4237 + EMail: gmalkin@baynetworks.com + + + Art Harkin + Internet Services Project + Information Networks Division + 19420 Homestead Road MS 43LN + Cupertino, CA 95014 + + Phone: (408) 447-3755 + EMail: ash@cup.hp.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Malkin & Harkin Standards Track [Page 4] + +RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Malkin & Harkin Standards Track [Page 5] + diff --git a/setup.py b/setup.py index 4b6a9e9..015a02c 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup setup(name='tftpy', - version='0.4.5', + version='0.4.6', description='Python TFTP library', author='Michael P. Soulier', author_email='msoulier@digitaltorque.ca', diff --git a/tftpy/TftpClient.py b/tftpy/TftpClient.py index c64245b..5ce3b32 100644 --- a/tftpy/TftpClient.py +++ b/tftpy/TftpClient.py @@ -97,6 +97,13 @@ class TftpClient(TftpSession): % (raddress, rport, self.host, self.port)) continue + + # If there is a packethook defined, call it. We unconditionally + # pass all packets, it's up to the client to screen out different + # kinds of packets. This way, the client is privy to things like + # negotiated options. + if packethook: + packethook(recvpkt) if not self.port and self.state.state == 'rrq': self.port = rport @@ -126,11 +133,8 @@ class TftpClient(TftpSession): % len(recvpkt.data)) outputfile.write(recvpkt.data) bytes += len(recvpkt.data) - # If there is a packethook defined, call it. - if packethook: - packethook(recvpkt) # Check for end-of-file, any less than full data packet. - if len(recvpkt.data) < self.options['blksize']: + if len(recvpkt.data) < int(self.options['blksize']): logger.info("end of file detected") break @@ -165,6 +169,8 @@ class TftpClient(TftpSession): if recvpkt.options.keys() > 0: if recvpkt.match_options(self.options): logger.info("Successful negotiation of options") + # Set options to OACK options + self.options = recvpkt.options for key in self.options: logger.info(" %s = %s" % (key, self.options[key])) logger.debug("sending ACK to OACK") diff --git a/tftpy/TftpPacketTypes.py b/tftpy/TftpPacketTypes.py index 53aa113..8577e3c 100644 --- a/tftpy/TftpPacketTypes.py +++ b/tftpy/TftpPacketTypes.py @@ -28,7 +28,7 @@ class TftpPacketWithOptions(object): goal is just to share code here, and not cause diamond inheritance.""" def __init__(self): - self.options = None + self.options = [] def setoptions(self, options): logger.debug("in TftpPacketWithOptions.setoptions") @@ -41,11 +41,11 @@ class TftpPacketWithOptions(object): % (newkey, myoptions[newkey])) logger.debug("setting options hash to: " + str(myoptions)) - self.__options = myoptions + self._options = myoptions def getoptions(self): logger.debug("in TftpPacketWithOptions.getoptions") - return self.__options + return self._options # Set up getter and setter on options to ensure that they are the proper # type. They should always be strings, but we don't need to force the @@ -123,6 +123,7 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): they share quite a bit of code.""" def __init__(self): TftpPacket.__init__(self) + TftpPacketWithOptions.__init__(self) self.filename = None self.mode = None @@ -151,11 +152,14 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): logger.debug("there are options to encode") for key in self.options: format += "%dsx" % len(key) - format += "%dsx" % len(str(self.options[key])) options_list.append(key) - options_list.append(str(self.options[key])) + # Not all options have values. + if key != 'tsize': + format += "%dsx" % len(str(self.options[key])) + options_list.append(str(self.options[key])) logger.debug("format is %s" % format) + logger.debug("options_list is %s" % options_list) logger.debug("size of struct is %d" % struct.calcsize(format)) self.buffer = struct.pack(format, @@ -212,23 +216,37 @@ class TftpPacketRRQ(TftpPacketInitial): 2 bytes string 1 byte string 1 byte ----------------------------------------------- RRQ/ | 01/02 | Filename | 0 | Mode | 0 | -WRQ ----------------------------------------------- +WRQ ----------------------------------------------- """ def __init__(self): TftpPacketInitial.__init__(self) self.opcode = 1 + def __str__(self): + s = 'RRQ packet: filename = %s' % self.filename + s += ' mode = %s' % self.mode + if self.options: + s += '\n options = %s' % self.options + return s + class TftpPacketWRQ(TftpPacketInitial): """ 2 bytes string 1 byte string 1 byte ----------------------------------------------- RRQ/ | 01/02 | Filename | 0 | Mode | 0 | -WRQ ----------------------------------------------- +WRQ ----------------------------------------------- """ def __init__(self): TftpPacketInitial.__init__(self) self.opcode = 2 + def __str__(self): + s = 'WRQ packet: filename = %s' % self.filename + s += ' mode = %s' % self.mode + if self.options: + s += '\n options = %s' % self.options + return s + class TftpPacketDAT(TftpPacket): """ 2 bytes 2 bytes n bytes @@ -242,6 +260,12 @@ DATA | 03 | Block # | Data | self.blocknumber = 0 self.data = None + def __str__(self): + s = 'DAT packet: block %s' % self.blocknumber + if self.data: + s += '\n data: %d bytes' % len(self.data) + return s + def encode(self): """Encode the DAT packet. This method populates self.buffer, and returns self for easy method chaining.""" @@ -281,6 +305,9 @@ ACK | 04 | Block # | self.opcode = 4 self.blocknumber = 0 + def __str__(self): + return 'ACK packet: block %d' % self.blocknumber + def encode(self): logger.debug("encoding ACK: opcode = %d, block = %d" % (self.opcode, self.blocknumber)) @@ -330,6 +357,9 @@ ERROR | 05 | ErrorCode | ErrMsg | 0 | 8: "Failed to negotiate options" } + def __str__(self): + return 'ERR packet: errorcode = %d' % self.errorcode + def encode(self): """Encode the DAT packet based on instance variables, populating self.buffer, returning self.""" @@ -362,7 +392,11 @@ class TftpPacketOACK(TftpPacket, TftpPacketWithOptions): """ def __init__(self): TftpPacket.__init__(self) + TftpPacketWithOptions.__init__(self) self.opcode = 6 + + def __str__(self): + return 'OACK packet:\n options = %s' % self.options def encode(self): format = "!H" # opcode diff --git a/tftpy/TftpServer.py b/tftpy/TftpServer.py index b597e4b..a5b8e5c 100644 --- a/tftpy/TftpServer.py +++ b/tftpy/TftpServer.py @@ -320,7 +320,7 @@ class TftpServerHandler(TftpSession): % (self.key, blksize)) self.options['blksize'] = DEF_BLKSIZE if recvpkt.options.has_key('tsize'): - logger.debug('RRQ includes received tsize') + logger.debug('RRQ includes tsize option') self.options['tsize'] = os.stat(self.filename).st_size self.send_oack()