summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--html/index.html2
-rw-r--r--t/test.py38
-rw-r--r--tftpy/TftpContexts.py5
-rw-r--r--tftpy/TftpStates.py134
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>
diff --git a/t/test.py b/t/test.py
index 066d90a..3c5bd47 100644
--- a/t/test.py
+++ b/t/test.py
@@ -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):