From 9e2750095a1662e62b3367d26b5ffee2146d8879 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 31 Oct 2014 13:53:21 +0100 Subject: [PATCH 1/3] Add the possibility to use a dynamic file loader It is useful for handling file out of the TFTP root or, in my case generate grub menus dynamically. --- ptftplib/tftpserver.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/ptftplib/tftpserver.py b/ptftplib/tftpserver.py index bfa46da..40f4794 100755 --- a/ptftplib/tftpserver.py +++ b/ptftplib/tftpserver.py @@ -145,8 +145,20 @@ def serveRRQ(self, op, request): return self.finish_state(peer_state) try: - peer_state.file = open(peer_state.filepath, 'rb') - peer_state.filesize = os.stat(peer_state.filepath)[stat.ST_SIZE] + # If the file exists, open it + if os.path.isfile(peer_state.filepath) and\ + os.access(peer_state.filepath, os.R_OK): + peer_state.file = open(peer_state.filepath, 'rb') + peer_state.filesize = os.stat(peer_state.filepath)[stat.ST_SIZE] + else: + # The file doen't exist, try the dynamic_file_handler + # if it is set + if hasattr(self, 'dynamic_file_handler') and\ + self.dynamic_file_handler is not None: + peer_state.file, peer_state.filesize = self.dynamic_file_handler(peer_state.filepath) + else: + raise IOError('Cannot access file: %s' % peer_state.filepath) + peer_state.packetnum = 0 peer_state.state = state.STATE_SEND @@ -468,7 +480,7 @@ def run(self): class TFTPServer(object): def __init__(self, iface, root, port=_PTFTPD_DEFAULT_PORT, - strict_rfc1350=False, notification_callbacks={}): + strict_rfc1350=False, notification_callbacks={}, dynamic_file_callback=None): self.iface, self.root, self.port, self.strict_rfc1350 = \ iface, root, port, strict_rfc1350 self.client_registry = {} @@ -477,8 +489,11 @@ def __init__(self, iface, root, port=_PTFTPD_DEFAULT_PORT, raise TFTPServerConfigurationError( "The specified TFTP root does not exist") + class _TFTPServerHandler(TFTPServerHandler): + dynamic_file_handler = dynamic_file_callback + self.ip, self.netmask, self.mac = get_ip_config_for_interface(self.iface) - self.server = SocketServer.UDPServer((self.ip, port), TFTPServerHandler) + self.server = SocketServer.UDPServer((self.ip, port), _TFTPServerHandler) self.server.root = self.root self.server.strict_rfc1350 = self.strict_rfc1350 self.server.clients = self.client_registry From 1a50bf694153a4836b5ac412264f131ece13f38b Mon Sep 17 00:00:00 2001 From: Robert Penz Date: Mon, 23 Feb 2015 09:08:27 +0100 Subject: [PATCH 2/3] workaround for python bug which leads to empty packets --- ptftplib/tftpserver.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ptftplib/tftpserver.py b/ptftplib/tftpserver.py index 40f4794..1d818e7 100755 --- a/ptftplib/tftpserver.py +++ b/ptftplib/tftpserver.py @@ -110,6 +110,12 @@ def handle(self): self.wfile.write(response) self.wfile.flush() + def finish(self): + """ Workaround for following bug: http://bugs.python.org/issue1767511 """ + wdata = self.wfile.getvalue() + if wdata: + self.socket.sendto(wdata, self.client_address) + def finish_state(self, peer_state): self.server.clients[self.client_address] = peer_state return peer_state.next() From 0dedbc0d0492c496baedd6790a2328fe9f9f4f38 Mon Sep 17 00:00:00 2001 From: Robert Penz Date: Mon, 23 Feb 2015 09:32:53 +0100 Subject: [PATCH 3/3] improvements to the serveRRQ() --- ptftplib/tftpserver.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ptftplib/tftpserver.py b/ptftplib/tftpserver.py index 1d818e7..0e7b792 100755 --- a/ptftplib/tftpserver.py +++ b/ptftplib/tftpserver.py @@ -133,11 +133,23 @@ def serveRRQ(self, op, request): """ try: - filename, mode, opts = proto.TFTPHelper.parseRRQ(request) + filenameOrig, mode, opts = proto.TFTPHelper.parseRRQ(request) except SyntaxError: # Ignore malformed RRQ requests return None + # we keep the filenameOrig for dynamic handler + # some clients request "\" and not "/" in their pathes + if os.name != 'nt': + filename = filenameOrig.replace('\\', '/') + else: + filename = filenameOrig.replace('/', '\\') + + # absolute path requests are always relative to the tftp root directory + # if the client does something nasty we get it some lines down. + if filename and filename[0] == "/": + filename = filename[1:] + peer_state = state.TFTPState(self.client_address, op, self.server.root, filename, mode, not self.server.strict_rfc1350) @@ -152,6 +164,7 @@ def serveRRQ(self, op, request): try: # If the file exists, open it + # TODO: Windows clients request case insensitive, which may be a problem on *nix servers if os.path.isfile(peer_state.filepath) and\ os.access(peer_state.filepath, os.R_OK): peer_state.file = open(peer_state.filepath, 'rb') @@ -161,9 +174,10 @@ def serveRRQ(self, op, request): # if it is set if hasattr(self, 'dynamic_file_handler') and\ self.dynamic_file_handler is not None: - peer_state.file, peer_state.filesize = self.dynamic_file_handler(peer_state.filepath) + # we send the original requested filename to the handler + peer_state.file, peer_state.filesize = self.dynamic_file_handler(filenameOrig) else: - raise IOError('Cannot access file: %s' % peer_state.filepath) + raise IOError('Cannot access file: %s' % filenameOrig) peer_state.packetnum = 0 peer_state.state = state.STATE_SEND