diff options
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | README | 5 | ||||
-rwxr-xr-x | bin/tftpy_client.py | 4 | ||||
-rwxr-xr-x | bin/tftpy_server.py | 3 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | t/README.txt | 0 | ||||
-rw-r--r-- | t/test.py | 30 | ||||
-rw-r--r-- | t/testWin.py | 111 | ||||
-rw-r--r-- | tftpy/TftpClient.py | 22 | ||||
-rw-r--r-- | tftpy/TftpContexts.py | 49 | ||||
-rw-r--r-- | tftpy/TftpPacketFactory.py | 13 | ||||
-rw-r--r-- | tftpy/TftpPacketTypes.py | 90 | ||||
-rw-r--r-- | tftpy/TftpServer.py | 81 | ||||
-rw-r--r-- | tftpy/TftpShared.py | 2 | ||||
-rw-r--r-- | tftpy/TftpStates.py | 64 | ||||
-rw-r--r-- | tftpy/__init__.py | 2 |
16 files changed, 324 insertions, 163 deletions
@@ -1,5 +1,10 @@ +.idea
+.DS_Store +
*.pyc +
*.swp -tags -.DS_Store +tags
+build +
test.log @@ -1,5 +1,10 @@ Copyright, Michael P. Soulier, 2010. +About Release 0.6.3_fork: +==================== +Maintenance release to fix a couple of bugs. +Don't try to run t/test.py in Win Environment use instead t/testWin.py + About Release 0.6.2: ==================== Maintenance release to fix a couple of reported issues. diff --git a/bin/tftpy_client.py b/bin/tftpy_client.py index 1a50b56..9eacd0d 100755 --- a/bin/tftpy_client.py +++ b/bin/tftpy_client.py @@ -4,6 +4,7 @@ import sys, logging, os from optparse import OptionParser import tftpy + def main(): usage="" parser = OptionParser(usage=usage) @@ -67,6 +68,7 @@ def main(): def __init__(self, out): self.progress = 0 self.out = out + def progresshook(self, pkt): if isinstance(pkt, tftpy.TftpPacketDAT): self.progress += len(pkt.data) @@ -105,7 +107,7 @@ def main(): tclient.upload(options.upload, options.input, progresshook) - except tftpy.TftpException, err: + except tftpy.TftpException as err: sys.stderr.write("%s\n" % str(err)) sys.exit(1) except KeyboardInterrupt: diff --git a/bin/tftpy_server.py b/bin/tftpy_server.py index 6a1d3a3..8c81def 100755 --- a/bin/tftpy_server.py +++ b/bin/tftpy_server.py @@ -4,6 +4,7 @@ import sys, logging from optparse import OptionParser import tftpy + def main(): usage="" parser = OptionParser(usage=usage) @@ -41,7 +42,7 @@ def main(): server = tftpy.TftpServer(options.root) try: server.listen(options.ip, options.port) - except tftpy.TftpException, err: + except tftpy.TftpException as err: sys.stderr.write("%s\n" % str(err)) sys.exit(1) except KeyboardInterrupt: @@ -3,7 +3,7 @@ from distutils.core import setup setup(name='tftpy', - version='0.6.2', + version='0.6.3_fork', description='Python TFTP library', author='Michael P. Soulier', author_email='msoulier@digitaltorque.ca', diff --git a/t/README.txt b/t/README.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/t/README.txt @@ -9,6 +9,7 @@ import threading log = tftpy.log + class TestTftpyClasses(unittest.TestCase): def setUp(self): @@ -64,7 +65,6 @@ class TestTftpyClasses(unittest.TestCase): self.assertEqual(wrq.mode, "octet", "Mode correct") self.assertEqual(wrq.options['blksize'], '1024', "Blksize correct") - def testTftpPacketDAT(self): log.debug("===> Running testcase testTftpPacketDAT") dat = tftpy.TftpPacketDAT() @@ -134,8 +134,9 @@ class TestTftpyClasses(unittest.TestCase): factory = tftpy.TftpPacketFactory() for opcode in classes: self.assert_(isinstance(factory._TftpPacketFactory__create(opcode), - classes[opcode]), - "opcode %d returns the correct class" % opcode) + classes[opcode]), + "opcode %d returns the correct class" % opcode) + class TestTftpyState(unittest.TestCase): @@ -162,12 +163,10 @@ class TestTftpyState(unittest.TestCase): # parent - let the server start try: time.sleep(1) - client.upload(filename, - input) + client.upload(filename, input) finally: os.kill(child_pid, 15) os.waitpid(child_pid, 0) - else: server.listen('localhost', 20001) @@ -237,8 +236,7 @@ class TestTftpyState(unittest.TestCase): timeout, root) - self.assertTrue( isinstance(serverstate, - tftpy.TftpContextServer) ) + self.assertTrue(isinstance(serverstate, tftpy.TftpContextServer)) rrq = tftpy.TftpPacketRRQ() rrq.filename = '640KBFILE' @@ -372,12 +370,13 @@ class TestTftpyState(unittest.TestCase): signal.alarm(2) try: server.listen('localhost', 20001) - except Exception, err: + except Exception as err: self.assertTrue( err[0] == 4 ) - def testServerDownloadWithStopNotNow(self, output='/tmp/out'): + def testServerDownloadWithStopNotNow(self): log.debug("===> Running testcase testServerDownloadWithStopNotNow") root = os.path.dirname(os.path.abspath(__file__)) + output='/tmp/out' server = tftpy.TftpServer(root) client = tftpy.TftpClient('localhost', 20001, @@ -403,17 +402,19 @@ class TestTftpyState(unittest.TestCase): else: import signal + def handlealarm(signum, frame): server.stop(now=False) signal.signal(signal.SIGALRM, handlealarm) signal.alarm(2) try: server.listen('localhost', 20001) - except Exception, err: - self.assertTrue( False, "Server should not exit early" ) + except Exception as err: + self.assertTrue(False, "Server should not exit early. %s" % err) - def testServerDownloadWithDynamicPort(self, output='/tmp/out'): + def testServerDownloadWithDynamicPort(self): log.debug("===> Running testcase testServerDownloadWithDynamicPort") + output='/tmp/out' root = os.path.dirname(os.path.abspath(__file__)) server = tftpy.TftpServer(root) @@ -426,8 +427,7 @@ class TestTftpyState(unittest.TestCase): server.is_running.wait() client = tftpy.TftpClient('localhost', server.listenport, {}) time.sleep(1) - client.download('640KBFILE', - output) + client.download('640KBFILE', output) finally: server.stop(now=False) server_thread.join() diff --git a/t/testWin.py b/t/testWin.py new file mode 100644 index 0000000..46ee0ad --- /dev/null +++ b/t/testWin.py @@ -0,0 +1,111 @@ +"""Unit tests for tftpy.""" + +import unittest +import logging +import tftpy +import os +import time +import threading + +log = tftpy.log + + +class TestTftpyState(unittest.TestCase): + + t_path = os.path.dirname(os.path.abspath(__file__)) + t_640KB = os.path.join(t_path, "640KBFILE") + t_out = os.path.join(t_path, "out.tmp") + + def setUp(self): + tftpy.setLogLevel(logging.DEBUG) + + def tearDown(self): + if os.path.exists(self.t_out): + os.remove(self.t_out) + + def _clientServerUploadOptions(self, options, upload_file=None): + """Fire up a server and a client and do a upload.""" + input_path = self.t_640KB + if not upload_file: + upload_file = input_path + + server = tftpy.TftpServer(self.t_path) + client = tftpy.TftpClient('localhost', 20001, options) + server_thread = threading.Thread(group=None, target=server.listen, + kwargs={'listenip': 'localhost', + 'listenport': 20001, + 'timeout': 10}) + server_thread.start() + server.is_running.wait() + try: + time.sleep(1) + client.upload("out.tmp", upload_file) + finally: + server.stop(now=False) + server_thread.join() + + def _clientServerDownloadOptions(self, options, output='/tmp/out'): + """Fire up a server and a client and do a download.""" + if isinstance(output, basestring) and not os.path.exists(output): + output = self.t_out + server = tftpy.TftpServer(self.t_path) + client = tftpy.TftpClient('localhost', 20001, options) + server_thread = threading.Thread(group=None, target=server.listen, + kwargs={'listenip': 'localhost', + 'listenport': 20001, + 'timeout': 10}) + server_thread.start() + server.is_running.wait() + try: + time.sleep(1) + client.download('640KBFILE', output) + finally: + server.stop(now=False) + server_thread.join() + + def testClientServerUploadNoOptions(self): + self._clientServerUploadOptions({}) + + def testClientServerUploadFileObj(self): + with open(self.t_640KB, 'r') as f: + self._clientServerUploadOptions({}, upload_file=f) + + def testClientServerUploadOptions(self): + for blksize in [512, 1024, 2048, 4096]: + self._clientServerUploadOptions({'blksize': blksize}) + + def testClientServerNoOptions(self): + self._clientServerDownloadOptions({}) + + def testClientFileObject(self): + with open(self.t_out, 'wb') as f: + self._clientServerDownloadOptions({}, f) + + def testClientServerBlksize(self): + for blksize in [512, 1024, 2048, 4096]: + self._clientServerDownloadOptions({'blksize': blksize}) + + def testClientServerNoOptionsDelay(self): + tftpy.TftpStates.DELAY_BLOCK = 10 + self._clientServerDownloadOptions({}) + tftpy.TftpStates.DELAY_BLOCK = 0 + + def testServerDownloadWithDynamicPort(self): + server = tftpy.TftpServer(self.t_path) + server_thread = threading.Thread(target=server.listen, + kwargs={'listenip': 'localhost', + 'listenport': 0}) + server_thread.start() + server.is_running.wait() + log.debug("> Tftp Server Listening at {}:{}".format(server.listenip, + server.listenport)) + client = tftpy.TftpClient('localhost', server.listenport, {}) + try: + time.sleep(1) + client.download('640KBFILE', self.t_out) + finally: + server.stop(now=False) + server_thread.join() + +if __name__ == '__main__': + unittest.main() diff --git a/tftpy/TftpClient.py b/tftpy/TftpClient.py index a935b34..a95d023 100644 --- a/tftpy/TftpClient.py +++ b/tftpy/TftpClient.py @@ -3,9 +3,15 @@ instance of the client, and then use its upload or download method. Logging is performed via a standard logging object set in TftpShared.""" import types -from TftpShared import * -from TftpPacketTypes import * -from TftpContexts import TftpContextClientDownload, TftpContextClientUpload +try: + from TftpShared import * + from TftpPacketTypes import * + from TftpContexts import TftpContextClientDownload, TftpContextClientUpload +except ImportError: + from tftpy.TftpShared import * + from tftpy.TftpPacketTypes import * + from tftpy.TftpContexts import TftpContextClientDownload, TftpContextClientUpload + class TftpClient(TftpSession): """This class is an implementation of a tftp client. Once instantiated, a @@ -19,11 +25,11 @@ class TftpClient(TftpSession): self.iport = port self.filename = None self.options = options - if self.options.has_key('blksize'): + if 'blksize' in self.options: size = self.options['blksize'] tftpassert(types.IntType == type(size), "blksize must be an int") if size < MIN_BLKSIZE or size > MAX_BLKSIZE: - raise TftpException, "Invalid blksize: %d" % size + raise TftpException("Invalid blksize: %d" % size) def download(self, filename, output, packethook=None, timeout=SOCK_TIMEOUT): """This method initiates a tftp download from the configured remote @@ -38,10 +44,8 @@ class TftpClient(TftpSession): Note: If output is a hyphen, stdout is used.""" # We're downloading. log.debug("Creating download context with the following params:") - log.debug("host = %s, port = %s, filename = %s, output = %s", - self.host, self.iport, filename, output) - log.debug("options = %s, packethook = %s, timeout = %s", - self.options, packethook, timeout) + log.debug("host = %s, port = %s, filename = %s" % (self.host, self.iport, filename)) + log.debug("options = %s, packethook = %s, timeout = %s" % (self.options, packethook, timeout)) self.context = TftpContextClientDownload(self.host, self.iport, filename, diff --git a/tftpy/TftpContexts.py b/tftpy/TftpContexts.py index eed1cd1..9dfe6ee 100644 --- a/tftpy/TftpContexts.py +++ b/tftpy/TftpContexts.py @@ -8,11 +8,19 @@ the next packet in the transfer, and returns a state object until the transfer is complete, at which point it returns None. That is, unless there is a fatal error, in which case a TftpException is returned instead.""" -from TftpShared import * -from TftpPacketTypes import * -from TftpPacketFactory import TftpPacketFactory -from TftpStates import * -import socket, time, sys +import sys +import time +import socket +try: + from TftpShared import * + from TftpPacketTypes import * + from TftpPacketFactory import TftpPacketFactory + from TftpStates import * +except ImportError: + from tftpy.TftpShared import * + from tftpy.TftpPacketTypes import * + from tftpy.TftpPacketFactory import TftpPacketFactory + from tftpy.TftpStates import * ############################################################################### # Utility classes @@ -112,10 +120,10 @@ class TftpContext(object): if we're over the timeout time.""" log.debug("checking for timeout on session %s", self) if now - self.last_update > self.timeout: - raise TftpTimeout, "Timeout waiting for traffic" + raise TftpTimeout("Timeout waiting for traffic") def start(self): - raise NotImplementedError, "Abstract method" + raise NotImplementedError("Abstract method") def end(self): """Perform session cleanup, since the end method should always be @@ -157,7 +165,7 @@ class TftpContext(object): (buffer, (raddress, rport)) = self.sock.recvfrom(MAX_BLKSIZE) except socket.timeout: log.warn("Timeout waiting for traffic, retrying...") - raise TftpTimeout, "Timed-out waiting for traffic" + raise TftpTimeout("Timed-out waiting for traffic") # Ok, we've received a packet. Log it. log.debug("Received %d bytes from %s:%s", @@ -265,8 +273,8 @@ class TftpContextClientUpload(TftpContext): self.fileobj = open(input, "rb") log.debug("TftpContextClientUpload.__init__()") - log.debug("file_to_transfer = %s, options = %s", - self.file_to_transfer, self.options) + log.debug("file_to_transfer = %s, options = %s" % + (self.file_to_transfer, self.options)) def __str__(self): return "%s:%s %s" % (self.host, self.port, self.state) @@ -277,7 +285,7 @@ class TftpContextClientUpload(TftpContext): log.info(" options -> %s" % self.options) self.metrics.start_time = time.time() - log.debug("Set metrics.start_time to %s", self.metrics.start_time) + log.debug("Set metrics.start_time to %s" % self.metrics.start_time) # FIXME: put this in a sendWRQ method? pkt = TftpPacketWRQ() @@ -294,9 +302,9 @@ class TftpContextClientUpload(TftpContext): while self.state: try: - log.debug("State is %s", self.state) + log.debug("State is %s" % self.state) self.cycle() - except TftpTimeout, err: + except TftpTimeout as err: log.error(str(err)) self.retry_count += 1 if self.retry_count >= TIMEOUT_RETRIES: @@ -310,9 +318,10 @@ class TftpContextClientUpload(TftpContext): """Finish up the context.""" TftpContext.end(self) self.metrics.end_time = time.time() - log.debug("Set metrics.end_time to %s", self.metrics.end_time) + log.debug("Set metrics.end_time to %s" % self.metrics.end_time) self.metrics.compute() + class TftpContextClientDownload(TftpContext): """The download context for the client during a download. Note: If output is a hyphen, then the output will be sent to stdout.""" @@ -343,8 +352,8 @@ class TftpContextClientDownload(TftpContext): self.fileobj = open(output, "wb") log.debug("TftpContextClientDownload.__init__()") - log.debug("file_to_transfer = %s, options = %s", - self.file_to_transfer, self.options) + log.debug("file_to_transfer = %s, options = %s" % + (self.file_to_transfer, self.options)) def __str__(self): return "%s:%s %s" % (self.host, self.port, self.state) @@ -356,7 +365,7 @@ class TftpContextClientDownload(TftpContext): log.info(" options -> %s" % self.options) self.metrics.start_time = time.time() - log.debug("Set metrics.start_time to %s", self.metrics.start_time) + log.debug("Set metrics.start_time to %s" % self.metrics.start_time) # FIXME: put this in a sendRRQ method? pkt = TftpPacketRRQ() @@ -371,9 +380,9 @@ class TftpContextClientDownload(TftpContext): while self.state: try: - log.debug("State is %s", self.state) + log.debug("State is %s" % self.state) self.cycle() - except TftpTimeout, err: + except TftpTimeout as err: log.error(str(err)) self.retry_count += 1 if self.retry_count >= TIMEOUT_RETRIES: @@ -387,5 +396,5 @@ class TftpContextClientDownload(TftpContext): """Finish up the context.""" TftpContext.end(self) self.metrics.end_time = time.time() - log.debug("Set metrics.end_time to %s", self.metrics.end_time) + log.debug("Set metrics.end_time to %s" % self.metrics.end_time) self.metrics.compute() diff --git a/tftpy/TftpPacketFactory.py b/tftpy/TftpPacketFactory.py index 154aec8..f32f6ad 100644 --- a/tftpy/TftpPacketFactory.py +++ b/tftpy/TftpPacketFactory.py @@ -2,8 +2,13 @@ buffer, and return the appropriate TftpPacket object to represent it, via the parse() method.""" -from TftpShared import * -from TftpPacketTypes import * +try: + from TftpShared import * + from TftpPacketTypes import * +except ImportError: + from tftpy.TftpShared import * + from tftpy.TftpPacketTypes import * + class TftpPacketFactory(object): """This class generates TftpPacket objects. It is responsible for parsing @@ -23,9 +28,9 @@ class TftpPacketFactory(object): """This method is used to parse an existing datagram into its corresponding TftpPacket object. The buffer is the raw bytes off of the network.""" - log.debug("parsing a %d byte packet", len(buffer)) + log.debug("parsing a %d byte packet" % len(buffer)) (opcode,) = struct.unpack("!H", buffer[:2]) - log.debug("opcode is %d", opcode) + log.debug("opcode is %d" % opcode) packet = self.__create(opcode) packet.buffer = buffer return packet.decode() diff --git a/tftpy/TftpPacketTypes.py b/tftpy/TftpPacketTypes.py index 4a2a0f1..66759cf 100644 --- a/tftpy/TftpPacketTypes.py +++ b/tftpy/TftpPacketTypes.py @@ -2,7 +2,10 @@ corresponding encode and decode methods for them.""" import struct -from TftpShared import * +try: + from TftpShared import * +except ImportError: + from tftpy.TftpShared import * class TftpSession(object): """This class is the base class for the tftp client and server. Any shared @@ -20,15 +23,15 @@ class TftpPacketWithOptions(object): def setoptions(self, options): log.debug("in TftpPacketWithOptions.setoptions") - log.debug("options: %s", str(options)) + log.debug("options: %s" % options) myoptions = {} for key in options: newkey = str(key) myoptions[newkey] = str(options[key]) - log.debug("populated myoptions with %s = %s", - newkey, myoptions[newkey]) + log.debug("populated myoptions with %s = %s" + % (newkey, myoptions[newkey])) - log.debug("setting options hash to: %s", str(myoptions)) + log.debug("setting options hash to: %s" % myoptions) self._options = myoptions def getoptions(self): @@ -47,8 +50,8 @@ class TftpPacketWithOptions(object): format = "!" options = {} - log.debug("decode_options: buffer is: %s", repr(buffer)) - log.debug("size of buffer is %d bytes", len(buffer)) + log.debug("decode_options: buffer is: %s" % repr(buffer)) + log.debug("size of buffer is %d bytes" % len(buffer)) if len(buffer) == 0: log.debug("size of buffer is zero, returning empty hash") return {} @@ -58,22 +61,22 @@ class TftpPacketWithOptions(object): length = 0 for c in buffer: if ord(c) == 0: - log.debug("found a null at length %d", length) + log.debug("found a null at length %d" % length) if length > 0: format += "%dsx" % length length = -1 else: - raise TftpException, "Invalid options in buffer" + raise TftpException("Invalid options in buffer") length += 1 - log.debug("about to unpack, format is: %s", format) + log.debug("about to unpack, format is: %s" % format) mystruct = struct.unpack(format, buffer) tftpassert(len(mystruct) % 2 == 0, "packet with odd number of option/value pairs") for i in range(0, len(mystruct), 2): - log.debug("setting option %s to %s", mystruct[i], mystruct[i+1]) + log.debug("setting option %s to %s" % (mystruct[i], mystruct[i+1])) options[mystruct[i]] = mystruct[i+1] return options @@ -92,7 +95,7 @@ class TftpPacket(object): order suitable for sending over the wire. This is an abstract method.""" - raise NotImplementedError, "Abstract method" + raise NotImplementedError("Abstract method") def decode(self): """The decode method of a TftpPacket takes a buffer off of the wire in @@ -102,7 +105,7 @@ class TftpPacket(object): datagram. This is an abstract method.""" - raise NotImplementedError, "Abstract method" + raise NotImplementedError("Abstract method") class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): """This class is a common parent class for the RRQ and WRQ packets, as @@ -121,17 +124,17 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): ptype = None if self.opcode == 1: ptype = "RRQ" else: ptype = "WRQ" - log.debug("Encoding %s packet, filename = %s, mode = %s", - ptype, self.filename, self.mode) + log.debug("Encoding %s packet, filename = %s, mode = %s" + % (ptype, self.filename, self.mode)) for key in self.options: - log.debug(" Option %s = %s", key, self.options[key]) + log.debug(" Option %s = %s" % (key, self.options[key])) format = "!H" format += "%dsx" % len(self.filename) if self.mode == "octet": format += "5sx" else: - raise AssertionError, "Unsupported mode: %s" % mode + raise AssertionError("Unsupported mode: %s" % mode) # Add options. options_list = [] if self.options.keys() > 0: @@ -144,9 +147,9 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): format += "%dsx" % len(str(self.options[key])) options_list.append(str(self.options[key])) - log.debug("format is %s", format) - log.debug("options_list is %s", options_list) - log.debug("size of struct is %d", struct.calcsize(format)) + log.debug("format is %s" % format) + log.debug("options_list is %s" % options_list) + log.debug("size of struct is %d" % struct.calcsize(format)) self.buffer = struct.pack(format, self.opcode, @@ -154,7 +157,7 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): self.mode, *options_list) - log.debug("buffer is %s", repr(self.buffer)) + log.debug("buffer is %s" % repr(self.buffer)) return self def decode(self): @@ -169,7 +172,8 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): for c in subbuf: if ord(c) == 0: nulls += 1 - log.debug("found a null at length %d, now have %d", length, nulls) + log.debug("found a null at length %d, now have %d" + % (length, nulls)) format += "%dsx" % length length = -1 # At 2 nulls, we want to mark that position for decoding. @@ -178,19 +182,19 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): length += 1 tlength += 1 - log.debug("hopefully found end of mode at length %d", tlength) + log.debug("hopefully found end of mode at length %d" % tlength) # length should now be the end of the mode. tftpassert(nulls == 2, "malformed packet") shortbuf = subbuf[:tlength+1] - log.debug("about to unpack buffer with format: %s", format) - log.debug("unpacking buffer: %s", repr(shortbuf)) + log.debug("about to unpack buffer with format: %s" % format) + log.debug("unpacking buffer: " + repr(shortbuf)) mystruct = struct.unpack(format, shortbuf) tftpassert(len(mystruct) == 2, "malformed packet") self.filename = mystruct[0] self.mode = mystruct[1].lower() # force lc - bug 17 - log.debug("set filename to %s", self.filename) - log.debug("set mode to %s", self.mode) + log.debug("set filename to %s" % self.filename) + log.debug("set mode to %s" % self.mode) self.options = self.decode_options(subbuf[tlength+1:]) return self @@ -274,11 +278,13 @@ class TftpPacketDAT(TftpPacket): # We know the first 2 bytes are the opcode. The second two are the # block number. (self.blocknumber,) = struct.unpack("!H", self.buffer[2:4]) - log.debug("decoding DAT packet, block number %d", self.blocknumber) - log.debug("should be %d bytes in the packet total", len(self.buffer)) + log.debug("decoding DAT packet, block number %d" % self.blocknumber) + log.debug("should be %d bytes in the packet total" + % len(self.buffer)) # Everything else is data. self.data = self.buffer[4:] - log.debug("found %d bytes of data", len(self.data)) + log.debug("found %d bytes of data" + % len(self.data)) return self class TftpPacketACK(TftpPacket): @@ -299,15 +305,15 @@ class TftpPacketACK(TftpPacket): return 'ACK packet: block %d' % self.blocknumber def encode(self): - log.debug("encoding ACK: opcode = %d, block = %d", - self.opcode, self.blocknumber) + log.debug("encoding ACK: opcode = %d, block = %d" + % (self.opcode, self.blocknumber)) self.buffer = struct.pack("!HH", self.opcode, self.blocknumber) return self def decode(self): self.opcode, self.blocknumber = struct.unpack("!HH", self.buffer) - log.debug("decoded ACK packet: opcode = %d, block = %d", - self.opcode, self.blocknumber) + log.debug("decoded ACK packet: opcode = %d, block = %d" + % (self.opcode, self.blocknumber)) return self class TftpPacketERR(TftpPacket): @@ -360,7 +366,7 @@ class TftpPacketERR(TftpPacket): """Encode the DAT packet based on instance variables, populating self.buffer, returning self.""" format = "!HH%dsx" % len(self.errmsgs[self.errorcode]) - log.debug("encoding ERR packet with format %s", format) + log.debug("encoding ERR packet with format %s" % format) self.buffer = struct.pack(format, self.opcode, self.errorcode, @@ -371,17 +377,17 @@ class TftpPacketERR(TftpPacket): "Decode self.buffer, populating instance variables and return self." buflen = len(self.buffer) tftpassert(buflen >= 4, "malformed ERR packet, too short") - log.debug("Decoding ERR packet, length %s bytes", buflen) + log.debug("Decoding ERR packet, length %s bytes" % buflen) if buflen == 4: log.debug("Allowing this affront to the RFC of a 4-byte packet") format = "!HH" - log.debug("Decoding ERR packet with format: %s", format) + log.debug("Decoding ERR packet with format: %s" % format) self.opcode, self.errorcode = struct.unpack(format, self.buffer) else: log.debug("Good ERR packet > 4 bytes") format = "!HH%dsx" % (len(self.buffer) - 5) - log.debug("Decoding ERR packet with format: %s", format) + log.debug("Decoding ERR packet with format: %s" % format) self.opcode, self.errorcode, self.errmsg = struct.unpack(format, self.buffer) log.error("ERR packet - errorcode: %d, message: %s" @@ -409,8 +415,8 @@ class TftpPacketOACK(TftpPacket, TftpPacketWithOptions): options_list = [] log.debug("in TftpPacketOACK.encode") for key in self.options: - log.debug("looping on option key %s", key) - log.debug("value is %s", self.options[key]) + log.debug("looping on option key %s" % key) + log.debug("value is %s" % self.options[key]) format += "%dsx" % len(key) format += "%dsx" % len(self.options[key]) options_list.append(key) @@ -434,8 +440,8 @@ class TftpPacketOACK(TftpPacket, TftpPacketWithOptions): # We can accept anything between the min and max values. size = self.options[name] if size >= MIN_BLKSIZE and size <= MAX_BLKSIZE: - log.debug("negotiated blksize of %d bytes", size) + log.debug("negotiated blksize of %d bytes" % size) options[blksize] = size else: - raise TftpException, "Unsupported option: %s" % name + raise TftpException("Unsupported option: %s" % name) return True diff --git a/tftpy/TftpServer.py b/tftpy/TftpServer.py index 12491a0..a119a1e 100644 --- a/tftpy/TftpServer.py +++ b/tftpy/TftpServer.py @@ -3,13 +3,22 @@ instance of the server, and then run the listen() method to listen for client requests. Logging is performed via a standard logging object set in TftpShared.""" -import socket, os, time +import os +import time +import socket import select import threading -from TftpShared import * -from TftpPacketTypes import * -from TftpPacketFactory import TftpPacketFactory -from TftpContexts import TftpContextServer +try: + from TftpShared import * + from TftpPacketTypes import * + from TftpPacketFactory import TftpPacketFactory + from TftpContexts import TftpContextServer +except ImportError: + from tftpy.TftpShared import * + from tftpy.TftpPacketTypes import * + from tftpy.TftpPacketFactory import TftpPacketFactory + from tftpy.TftpContexts import TftpContextServer + class TftpServer(TftpSession): """This class implements a tftp server object. Run the listen() method to @@ -38,27 +47,26 @@ class TftpServer(TftpSession): if self.dyn_file_func: if not callable(self.dyn_file_func): - raise TftpException, "A dyn_file_func supplied, but it is not callable." + raise TftpException("A dyn_file_func supplied, " + "but it is not callable.") elif os.path.exists(self.root): - log.debug("tftproot %s does exist", self.root) + log.debug("tftproot %s does exist" % self.root) if not os.path.isdir(self.root): - raise TftpException, "The tftproot must be a directory." + raise TftpException("The tftproot must be a directory.") else: - log.debug("tftproot %s is a directory", self.root) + log.debug("tftproot %s is a directory" % self.root) if os.access(self.root, os.R_OK): - log.debug("tftproot %s is readable", self.root) + log.debug("tftproot %s is readable" % self.root) else: - raise TftpException, "The tftproot must be readable" + raise TftpException("The tftproot must be readable") if os.access(self.root, os.W_OK): - log.debug("tftproot %s is writable", self.root) + log.debug("tftproot %s is writable" % self.root) else: log.warning("The tftproot %s is not writable" % self.root) else: - raise TftpException, "The tftproot does not exist." + raise TftpException("The tftproot does not exist.") - def listen(self, - listenip="", - listenport=DEF_TFTP_PORT, + def listen(self, listenip="", listenport=DEF_TFTP_PORT, timeout=SOCK_TIMEOUT): """Start a server listening on the supplied interface and port. This defaults to INADDR_ANY (all interfaces) and UDP port 69. You can also @@ -68,25 +76,25 @@ class TftpServer(TftpSession): # Don't use new 2.5 ternary operator yet # listenip = listenip if listenip else '0.0.0.0' if not listenip: listenip = '0.0.0.0' - log.info("Server requested on ip %s, port %s" - % (listenip, listenport)) + log.info("Server requested on ip %s, port %s" % (listenip, listenport)) try: # FIXME - sockets should be non-blocking self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind((listenip, listenport)) _, self.listenport = self.sock.getsockname() - except socket.error, err: + except socket.error as err: # Reraise it for now. - raise + raise err self.is_running.set() log.info("Starting receive loop...") while True: - log.debug("shutdown_immediately is %s", self.shutdown_immediately) - log.debug("shutdown_gracefully is %s", self.shutdown_gracefully) + log.debug("shutdown_immediately is %s" % self.shutdown_immediately) + log.debug("shutdown_gracefully is %s" % self.shutdown_gracefully) if self.shutdown_immediately: - log.warn("Shutting down now. Session count: %d" % len(self.sessions)) + log.warn("Shutting down now. Session count: %d" % + len(self.sessions)) self.sock.close() for key in self.sessions: self.sessions[key].end() @@ -95,7 +103,8 @@ class TftpServer(TftpSession): elif self.shutdown_gracefully: if not self.sessions: - log.warn("In graceful shutdown mode and all sessions complete.") + log.warn("In graceful shutdown mode and all " + "sessions complete.") self.sock.close() break @@ -124,7 +133,8 @@ class TftpServer(TftpSession): log.debug("Read %d bytes", len(buffer)) if self.shutdown_gracefully: - log.warn("Discarding data on main port, in graceful shutdown mode") + log.warn("Discarding data on main port, " + "in graceful shutdown mode") continue # Forge a session key based on the client's IP and port, @@ -133,7 +143,7 @@ class TftpServer(TftpSession): if not self.sessions.has_key(key): log.debug("Creating new server context for " - "session key = %s", key) + "session key = %s" % key) self.sessions[key] = TftpContextServer(raddress, rport, timeout, @@ -141,7 +151,7 @@ class TftpServer(TftpSession): self.dyn_file_func) try: self.sessions[key].start(buffer) - except TftpException, err: + except TftpException as err: deletion_list.append(key) log.error("Fatal exception thrown from " "session %s: %s" % (key, str(err))) @@ -163,7 +173,7 @@ class TftpServer(TftpSession): if self.sessions[key].state == None: log.info("Successful transfer.") deletion_list.append(key) - except TftpException, err: + except TftpException as err: deletion_list.append(key) log.error("Fatal exception thrown from " "session %s: %s" @@ -171,7 +181,6 @@ class TftpServer(TftpSession): # Break out of for loop since we found the correct # session. break - else: log.error("Can't find the owner for this packet. " "Discarding.") @@ -181,15 +190,15 @@ class TftpServer(TftpSession): for key in self.sessions: try: self.sessions[key].checkTimeout(now) - except TftpTimeout, err: + except TftpTimeout as err: log.error(str(err)) self.sessions[key].retry_count += 1 if self.sessions[key].retry_count >= TIMEOUT_RETRIES: - log.debug("hit max retries on %s, giving up", + log.debug("hit max retries on %s, giving up" % self.sessions[key]) deletion_list.append(key) else: - log.debug("resending on session %s", self.sessions[key]) + log.debug("resending on session %s" % self.sessions[key]) self.sessions[key].state.resendLast() log.debug("Iterating deletion list.") @@ -208,12 +217,12 @@ class TftpServer(TftpSession): log.info("Average rate: %.2f kbps" % metrics.kbps) log.info("%.2f bytes in resent data" % metrics.resent_bytes) log.info("%d duplicate packets" % metrics.dupcount) - log.debug("Deleting session %s", key) + log.debug("Deleting session %s" % key) del self.sessions[key] - log.debug("Session list is now %s", self.sessions) + log.debug("Session list is now %s" % self.sessions) else: - log.warn("Strange, session %s is not on the deletion list" - % key) + log.warn( + "Strange, session %s is not on the deletion list" % key) self.is_running.clear() diff --git a/tftpy/TftpShared.py b/tftpy/TftpShared.py index d09d8bd..171ec1c 100644 --- a/tftpy/TftpShared.py +++ b/tftpy/TftpShared.py @@ -26,7 +26,7 @@ def tftpassert(condition, msg): with the message passed. This just makes the code throughout cleaner by refactoring.""" if not condition: - raise TftpException, msg + raise TftpException(msg) def setLogLevel(level): """This function is a utility function for setting the internal log level. diff --git a/tftpy/TftpStates.py b/tftpy/TftpStates.py index 1ea6c31..fe1a681 100644 --- a/tftpy/TftpStates.py +++ b/tftpy/TftpStates.py @@ -8,9 +8,13 @@ the next packet in the transfer, and returns a state object until the transfer is complete, at which point it returns None. That is, unless there is a fatal error, in which case a TftpException is returned instead.""" -from TftpShared import * -from TftpPacketTypes import * import os +try: + from TftpShared import * + from TftpPacketTypes import * +except: + from tftpy.TftpShared import * + from tftpy.TftpPacketTypes import * ############################################################################### # State classes @@ -28,7 +32,7 @@ class TftpState(object): def handle(self, pkt, raddress, rport): """An abstract method for handling a packet. It is expected to return a TftpState object, either itself or a new state.""" - raise NotImplementedError, "Abstract method" + raise NotImplementedError("Abstract method") def handleOACK(self, pkt): """This method handles an OACK from the server, syncing any accepted @@ -42,9 +46,9 @@ class TftpState(object): log.info(" %s = %s" % (key, self.context.options[key])) else: log.error("Failed to negotiate options") - raise TftpException, "Failed to negotiate options" + raise TftpException("Failed to negotiate options") else: - raise TftpException, "No options found in OACK" + raise TftpException("No options found in OACK") def returnSupportedOptions(self, options): """This method takes a requested options list from a client, and @@ -181,7 +185,7 @@ class TftpState(object): if pkt.blocknumber == 0: log.warn("There is no block zero!") self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "There is no block zero!" + raise TftpException("There is no block zero!") log.warn("Dropping duplicate block %d" % pkt.blocknumber) self.context.metrics.add_dup(pkt) log.debug("ACKing block %d again, just in case", pkt.blocknumber) @@ -192,7 +196,7 @@ class TftpState(object): msg = "Whoa! Received future block %d but expected %d" \ % (pkt.blocknumber, self.context.next_block) log.error(msg) - raise TftpException, msg + raise TftpException(msg) # Default is to ack return TftpStateExpectDAT(self.context) @@ -231,8 +235,8 @@ class TftpServerState(TftpState): # FIXME - only octet mode is supported at this time. if pkt.mode != 'octet': self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, \ - "Only octet transfers are supported at this time." + raise TftpException( + "Only octet transfers are supported at this time.") # test host/port of client end if self.context.host != raddress or self.context.port != rport: @@ -276,7 +280,7 @@ class TftpServerState(TftpState): else: log.warn("requested file is not within the server root - bad") self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "bad file path" + raise TftpException("bad file path") self.context.file_to_transfer = pkt.filename @@ -305,10 +309,10 @@ class TftpStateServerRecvRRQ(TftpServerState): log.debug("dyn_file_func returned 'None', treating as " "FileNotFound") self.sendError(TftpErrors.FileNotFound) - raise TftpException, "File not found: %s" % path + raise TftpException("File not found: %s" % path) else: self.sendError(TftpErrors.FileNotFound) - raise TftpException, "File not found: %s" % path + raise TftpException("File not found: %s" % path) # Options negotiation. if sendoack: @@ -348,7 +352,7 @@ class TftpStateServerRecvWRQ(TftpServerState): if os.path.isdir(current): log.debug("%s is already an existing directory", current) else: - os.mkdir(current, 0700) + os.mkdir(current, 0o700) def handle(self, pkt, raddress, rport): "Handle an initial WRQ packet as a server." @@ -401,8 +405,8 @@ class TftpStateServerStart(TftpState): rport) else: self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, \ - "Invalid packet to begin up/download: %s" % pkt + raise TftpException( + "Invalid packet to begin up/download: %s" % pkt) class TftpStateExpectACK(TftpState): """This class represents the state of the transfer when a DAT was just @@ -437,8 +441,8 @@ class TftpStateExpectACK(TftpState): return self elif isinstance(pkt, TftpPacketERR): log.error("Received ERR packet from peer: %s" % str(pkt)) - raise TftpException, \ - "Received ERR packet from peer: %s" % str(pkt) + raise TftpException( + "Received ERR packet from peer: %s" % str(pkt)) else: log.warn("Discarding unsupported packet: %s" % str(pkt)) return self @@ -454,19 +458,19 @@ class TftpStateExpectDAT(TftpState): elif isinstance(pkt, TftpPacketACK): # Umm, we ACK, you don't. self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received ACK from peer when expecting DAT" + raise TftpException("Received ACK from peer when expecting DAT") elif isinstance(pkt, TftpPacketWRQ): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received WRQ from peer when expecting DAT" + raise TftpException("Received WRQ from peer when expecting DAT") elif isinstance(pkt, TftpPacketERR): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received ERR from peer: " + str(pkt) + raise TftpException("Received ERR from peer: " + str(pkt)) else: self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received unknown packet type from peer: " + str(pkt) + raise TftpException("Received unknown packet type from peer: " + str(pkt)) class TftpStateSentWRQ(TftpState): """Just sent an WRQ packet for an upload.""" @@ -509,19 +513,19 @@ class TftpStateSentWRQ(TftpState): elif isinstance(pkt, TftpPacketERR): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received ERR from server: " + str(pkt) + raise TftpException("Received ERR from server: %s" % pkt) elif isinstance(pkt, TftpPacketRRQ): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received RRQ from server while in upload" + raise TftpException("Received RRQ from server while in upload") elif isinstance(pkt, TftpPacketDAT): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received DAT from server while in upload" + raise TftpException("Received DAT from server while in upload") else: self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received unknown packet type from server: " + str(pkt) + raise TftpException("Received unknown packet type from server: %s" % pkt) # By default, no state change. return self @@ -539,7 +543,7 @@ class TftpStateSentRRQ(TftpState): log.info("Received OACK from server") try: self.handleOACK(pkt) - except TftpException, err: + except TftpException as err: log.error("Failed to negotiate options: %s" % str(err)) self.sendError(TftpErrors.FailedNegotiation) raise @@ -564,19 +568,19 @@ class TftpStateSentRRQ(TftpState): elif isinstance(pkt, TftpPacketACK): # Umm, we ACK, the server doesn't. self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received ACK from server while in download" + raise TftpException("Received ACK from server while in download") elif isinstance(pkt, TftpPacketWRQ): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received WRQ from server while in download" + raise TftpException("Received WRQ from server while in download") elif isinstance(pkt, TftpPacketERR): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received ERR from server: " + str(pkt) + raise TftpException("Received ERR from server: %s" % pkt) else: self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "Received unknown packet type from server: " + str(pkt) + raise TftpException("Received unknown packet type from server: %s" % pkt) # By default, no state change. return self diff --git a/tftpy/__init__.py b/tftpy/__init__.py index fba9a9f..a0b0f85 100644 --- a/tftpy/__init__.py +++ b/tftpy/__init__.py @@ -13,7 +13,7 @@ import sys # Make sure that this is at least Python 2.3 required_version = (2, 3) if sys.version_info < required_version: - raise ImportError, "Requires at least Python 2.3" + raise ImportError("Requires at least Python 2.3") from tftpy.TftpShared import * from tftpy.TftpPacketTypes import * |