diff options
-rw-r--r-- | html/index.html | 2 | ||||
-rw-r--r-- | t/test.py | 38 | ||||
-rw-r--r-- | tftpy/TftpContexts.py | 5 | ||||
-rw-r--r-- | tftpy/TftpStates.py | 134 |
4 files changed, 112 insertions, 67 deletions
diff --git a/html/index.html b/html/index.html index f2591f4..da9765e 100644 --- a/html/index.html +++ b/html/index.html @@ -53,7 +53,7 @@ <ul> <li> <a href="http://www.faqs.org/rfcs/rfc1350.html">1350</a> - The TFTP Protocol, revision 2 (downloads only, octet mode only) + The TFTP Protocol, revision 2 (octet mode only) </li> <li> <a href="http://www.faqs.org/rfcs/rfc2347.html">2347</a> @@ -286,5 +286,43 @@ class TestTftpyState(unittest.TestCase): finalstate = serverstate.state.handle(ack, raddress, rport) self.assertTrue( finalstate is None ) + def testServerInsecurePath(self): + raddress = '127.0.0.2' + rport = 10000 + timeout = 5 + root = os.path.dirname(os.path.abspath(__file__)) + serverstate = tftpy.TftpContextServer(raddress, + rport, + timeout, + root) + rrq = tftpy.TftpPacketRRQ() + rrq.filename = '../setup.py' + rrq.mode = 'octet' + rrq.options = {} + + # Start the download. + self.assertRaises(tftpy.TftpException, + serverstate.start, rrq.encode().buffer) + + def testServerSecurePath(self): + raddress = '127.0.0.2' + rport = 10000 + timeout = 5 + root = os.path.dirname(os.path.abspath(__file__)) + serverstate = tftpy.TftpContextServer(raddress, + rport, + timeout, + root) + rrq = tftpy.TftpPacketRRQ() + rrq.filename = '100KBFILE' + rrq.mode = 'octet' + rrq.options = {} + + # Start the download. + serverstate.start(rrq.encode().buffer) + # Should be in expectack state. + self.assertTrue(isinstance(serverstate.state, + tftpy.TftpStateExpectACK)) + if __name__ == '__main__': unittest.main() diff --git a/tftpy/TftpContexts.py b/tftpy/TftpContexts.py index 7f118d8..264c4c1 100644 --- a/tftpy/TftpContexts.py +++ b/tftpy/TftpContexts.py @@ -67,7 +67,7 @@ class TftpMetrics(object): class TftpContext(object): """The base class of the contexts.""" - def __init__(self, host, port, timeout, dyn_file_func=None): + def __init__(self, host, port, timeout): """Constructor for the base context, setting shared instance variables.""" self.file_to_transfer = None @@ -94,7 +94,6 @@ class TftpContext(object): self.last_update = 0 # The last packet we sent, if applicable, to make resending easy. self.last_pkt = None - self.dyn_file_func = dyn_file_func # Count the number of retry attempts. self.retry_count = 0 @@ -199,11 +198,11 @@ class TftpContextServer(TftpContext): host, port, timeout, - dyn_file_func ) # At this point we have no idea if this is a download or an upload. We # need to let the start state determine that. self.state = TftpStateServerStart(self) + self.root = root self.dyn_file_func = dyn_file_func diff --git a/tftpy/TftpStates.py b/tftpy/TftpStates.py index c9f20b5..8e4ad1e 100644 --- a/tftpy/TftpStates.py +++ b/tftpy/TftpStates.py @@ -24,9 +24,6 @@ class TftpState(object): file object is required, since in tftp there's always a file involved.""" self.context = context - # This variable is used to store the absolute path to the file being - # managed. Currently only used by the server. - self.full_path = None def handle(self, pkt, raddress, rport): """An abstract method for handling a packet. It is expected to return @@ -76,64 +73,6 @@ class TftpState(object): log.debug("Returning these accepted options: %s" % accepted_options) return accepted_options - def serverInitial(self, pkt, raddress, rport): - """This method performs initial setup for a server context transfer, - put here to refactor code out of the TftpStateServerRecvRRQ and - TftpStateServerRecvWRQ classes, since their initial setup is - identical. The method returns a boolean, sendoack, to indicate whether - it is required to send an OACK to the client.""" - options = pkt.options - sendoack = False - if not self.context.tidport: - self.context.tidport = rport - log.info("Setting tidport to %s" % rport) - - log.debug("Setting default options, blksize") - self.context.options = { 'blksize': DEF_BLKSIZE } - - if options: - log.debug("Options requested: %s" % options) - supported_options = self.returnSupportedOptions(options) - self.context.options.update(supported_options) - sendoack = True - - # 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." - - # test host/port of client end - if self.context.host != raddress or self.context.port != rport: - self.sendError(TftpErrors.UnknownTID) - log.error("Expected traffic from %s:%s but received it " - "from %s:%s instead." - % (self.context.host, - self.context.port, - raddress, - rport)) - # FIXME: increment an error count? - # Return same state, we're still waiting for valid traffic. - return self - - log.debug("Requested filename is %s" % pkt.filename) - - # Make sure that the path to the file is contained in the server's - # root directory. - full_path = os.path.join(self.context.root, pkt.filename) - self.full_path = os.path.abspath(full_path) - log.debug("full_path is %s" % full_path) - if self.context.root == full_path[:len(self.context.root)]: - log.info("requested file is in the server root - good") - else: - log.warn("requested file is not within the server root - bad") - self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException, "bad file path" - - self.context.file_to_transfer = pkt.filename - - return sendoack - def sendDAT(self): """This method sends the next DAT packet based on the data in the context. It returns a boolean indicating whether the transfer is @@ -261,7 +200,76 @@ class TftpState(object): # Default is to ack return TftpStateExpectDAT(self.context) -class TftpStateServerRecvRRQ(TftpState): +class TftpServerState(TftpState): + """The base class for server states.""" + + def __init__(self, context): + TftpState.__init__(self, context) + + # This variable is used to store the absolute path to the file being + # managed. + self.full_path = None + + def serverInitial(self, pkt, raddress, rport): + """This method performs initial setup for a server context transfer, + put here to refactor code out of the TftpStateServerRecvRRQ and + TftpStateServerRecvWRQ classes, since their initial setup is + identical. The method returns a boolean, sendoack, to indicate whether + it is required to send an OACK to the client.""" + options = pkt.options + sendoack = False + if not self.context.tidport: + self.context.tidport = rport + log.info("Setting tidport to %s" % rport) + + log.debug("Setting default options, blksize") + self.context.options = { 'blksize': DEF_BLKSIZE } + + if options: + log.debug("Options requested: %s" % options) + supported_options = self.returnSupportedOptions(options) + self.context.options.update(supported_options) + sendoack = True + + # 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." + + # test host/port of client end + if self.context.host != raddress or self.context.port != rport: + self.sendError(TftpErrors.UnknownTID) + log.error("Expected traffic from %s:%s but received it " + "from %s:%s instead." + % (self.context.host, + self.context.port, + raddress, + rport)) + # FIXME: increment an error count? + # Return same state, we're still waiting for valid traffic. + return self + + log.debug("Requested filename is %s" % pkt.filename) + + # Make sure that the path to the file is contained in the server's + # root directory. + full_path = os.path.join(self.context.root, pkt.filename) + self.full_path = os.path.abspath(full_path) + log.debug("full_path is %s" % full_path) + if self.full_path.startswith(self.context.root): + log.info("requested file is in the server root - good") + else: + log.warn("requested file is not within the server root - bad") + self.sendError(TftpErrors.IllegalTftpOp) + raise TftpException, "bad file path" + + self.context.file_to_transfer = pkt.filename + + return sendoack + + +class TftpStateServerRecvRRQ(TftpServerState): """This class represents the state of the TFTP server when it has just received an RRQ packet.""" def handle(self, pkt, raddress, rport): @@ -306,7 +314,7 @@ class TftpStateServerRecvRRQ(TftpState): # Note, we don't have to check any other states in this method, that's # up to the caller. -class TftpStateServerRecvWRQ(TftpState): +class TftpStateServerRecvWRQ(TftpServerState): """This class represents the state of the TFTP server when it has just received a WRQ packet.""" def make_subdirs(self): |