def test_error(self): # Zero-length payload self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, '') # One byte payload self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, '\x00') # Errorcode only (maybe this should fail) dgram = ERRORDatagram.from_wire('\x00\x01') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, errors[1]) # Errorcode with errstring - not terminated dgram = ERRORDatagram.from_wire('\x00\x01foobar') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, 'foobar') # Errorcode with errstring - terminated dgram = ERRORDatagram.from_wire('\x00\x01foobar\x00') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, 'foobar') # Unknown errorcode self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, '\x00\x0efoobar') # Unknown errorcode in from_code self.assertRaises(WireProtocolError, ERRORDatagram.from_code, 13) # from_code with custom message dgram = ERRORDatagram.from_code(3, "I've accidentally the whole message") self.assertEqual(dgram.errorcode, 3) self.assertEqual(dgram.errmsg, "I've accidentally the whole message") self.assertEqual(dgram.to_wire(), "\x00\x05\x00\x03I've accidentally the whole message\x00") # from_code default message dgram = ERRORDatagram.from_code(3) self.assertEqual(dgram.errorcode, 3) self.assertEqual(dgram.errmsg, "Disk full or allocation exceeded") self.assertEqual(dgram.to_wire(), "\x00\x05\x00\x03Disk full or allocation exceeded\x00")
def _startSession(self, datagram, addr, mode): # Set up a call context so that we can pass extra arbitrary # information to interested backends without adding extra call # arguments, or switching to using a request object, for example. context = {} if self.transport is not None: # Add the local and remote addresses to the call context. local = self.transport.getHost() context["local"] = local.host, local.port context["remote"] = addr try: if datagram.opcode == OP_WRQ: fs_interface = yield call(context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call(context, self.backend.get_reader, datagram.filename) except Unsupported as e: self.transport.write( ERRORDatagram.from_code( ERR_ILLEGAL_OP, u"{}".format(e).encode("ascii", "replace")).to_wire(), addr) except AccessViolation: self.transport.write( ERRORDatagram.from_code(ERR_ACCESS_VIOLATION).to_wire(), addr) except FileExists: self.transport.write( ERRORDatagram.from_code(ERR_FILE_EXISTS).to_wire(), addr) except FileNotFound: self.transport.write( ERRORDatagram.from_code(ERR_FILE_NOT_FOUND).to_wire(), addr) except BackendError as e: self.transport.write( ERRORDatagram.from_code( ERR_NOT_DEFINED, u"{}".format(e).encode("ascii", "replace")).to_wire(), addr) else: if datagram.opcode == OP_WRQ: if mode == b'netascii': fs_interface = NetasciiReceiverProxy(fs_interface) session = RemoteOriginWriteSession(addr, fs_interface, datagram.options, _clock=self._clock) reactor.listenUDP(0, session) returnValue(session) elif datagram.opcode == OP_RRQ: if mode == b'netascii': fs_interface = NetasciiSenderProxy(fs_interface) session = RemoteOriginReadSession(addr, fs_interface, datagram.options, _clock=self._clock) reactor.listenUDP(0, session) returnValue(session)
class TFTP(DatagramProtocol): """TFTP dispatch protocol. Handles read requests (RRQ) and write requests (WRQ) and starts the corresponding sessions. @ivar backend: an L{IBackend} provider, that will handle interaction with local resources @type backend: L{IBackend} provider """ def __init__(self, backend, _clock=None): self.backend = backend if _clock is None: self._clock = reactor else: self._clock = _clock def startProtocol(self): addr = self.transport.getHost() log.msg("TFTP Listener started at %s:%s" % (addr.host, addr.port)) def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if datagram.mode not in ('netascii', 'octet'): return self.transport.write( ERRORDatagram.from_code( ERR_ILLEGAL_OP, "Unknown transfer mode %s, - expected " "'netascii' or 'octet' (case-insensitive)" % mode).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode) @inlineCallbacks def _startSession(self, datagram, addr, mode): # Set up a call context so that we can pass extra arbitrary # information to interested backends without adding extra call # arguments, or switching to using a request object, for example. context = {} if self.transport is not None: # Add the local and remote addresses to the call context. local = self.transport.getHost() context["local"] = local.host, local.port context["remote"] = addr try: if datagram.opcode == OP_WRQ: fs_interface = yield call(context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call(context, self.backend.get_reader, datagram.filename) except Unsupported, e: self.transport.write( ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr) except AccessViolation: self.transport.write( ERRORDatagram.from_code(ERR_ACCESS_VIOLATION).to_wire(), addr)
def datagramReceived(self, datagram, addr): if self.remote[1] != addr[1]: self.transport.write(ERRORDatagram.from_code(ERR_TID_UNKNOWN).to_wire()) return# Does not belong to this transfer datagram = TFTPDatagramFactory(*split_opcode(datagram)) if datagram.opcode == OP_ERROR: return self.tftp_ERROR(datagram) return self._datagramReceived(datagram)
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if datagram.mode not in ('netascii', 'octet'): return self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, "Unknown transfer mode %s, - expected " "'netascii' or 'octet' (case-insensitive)" % mode).to_wire(), addr) try: if datagram.opcode == OP_WRQ: fs_interface = self.backend.get_writer(datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = self.backend.get_reader(datagram.filename) except Unsupported, e: return self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr)
def tftp_DATA(self, datagram): """Handle incoming DATA TFTP datagram @type datagram: L{DATADatagram} """ next_blocknum = self.blocknum + 1 if datagram.blocknum < next_blocknum: self.transport.write(ACKDatagram(datagram.blocknum).to_wire()) elif datagram.blocknum == next_blocknum: if self.completed: self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, "Transfer already finished").to_wire()) else: return self.nextBlock(datagram) else: self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, "Block number mismatch").to_wire())
def _startSession(self, datagram, addr, mode): try: if datagram.opcode == OP_WRQ: fs_interface = yield self.backend.get_writer(datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield self.backend.get_reader(datagram.filename) except Unsupported, e: self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr)
def tftp_DATA(self, datagram): """Handle incoming DATA TFTP datagram @type datagram: L{DATADatagram} """ next_blocknum = self.blocknum + 1 if datagram.blocknum < next_blocknum: self.transport.write(ACKDatagram(datagram.blocknum).to_wire()) elif datagram.blocknum == next_blocknum: if self.completed: self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, b"Transfer already finished").to_wire()) else: return self.nextBlock(datagram) else: self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, b"Block number mismatch").to_wire())
def _startSession(self, datagram, addr, mode): # Set up a call context so that we can pass extra arbitrary # information to interested backends without adding extra call # arguments, or switching to using a request object, for example. context = {} if self.transport is not None: # Add the local and remote addresses to the call context. local = self.transport.getHost() context["local"] = local.host, local.port context["remote"] = addr try: if datagram.opcode == OP_WRQ: fs_interface = yield call( context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call( context, self.backend.get_reader, datagram.filename) except Unsupported as e: self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, u"{}".format(e).encode("ascii", "replace")).to_wire(), addr) except AccessViolation: self.transport.write(ERRORDatagram.from_code(ERR_ACCESS_VIOLATION).to_wire(), addr) except FileExists: self.transport.write(ERRORDatagram.from_code(ERR_FILE_EXISTS).to_wire(), addr) except FileNotFound: self.transport.write(ERRORDatagram.from_code(ERR_FILE_NOT_FOUND).to_wire(), addr) except BackendError as e: self.transport.write(ERRORDatagram.from_code(ERR_NOT_DEFINED, u"{}".format(e).encode("ascii", "replace")).to_wire(), addr) else: if datagram.opcode == OP_WRQ: if mode == b'netascii': fs_interface = NetasciiReceiverProxy(fs_interface) session = RemoteOriginWriteSession(addr, fs_interface, datagram.options, _clock=self._clock) reactor.listenUDP(0, session) returnValue(session) elif datagram.opcode == OP_RRQ: if mode == b'netascii': fs_interface = NetasciiSenderProxy(fs_interface) session = RemoteOriginReadSession(addr, fs_interface, datagram.options, _clock=self._clock) reactor.listenUDP(0, session) returnValue(session)
def datagramReceived(self, datagram, addr): if self.remote[1] != addr[1]: self.transport.write(ERRORDatagram.from_code(ERR_TID_UNKNOWN).to_wire()) return# Does not belong to this transfer datagram = TFTPDatagramFactory(*split_opcode(datagram)) # TODO: Disabled for the time being. Performance degradation # and log file swamping was reported. # log.msg("Datagram received from %s: %s" % (addr, datagram)) if datagram.opcode == OP_ERROR: return self.tftp_ERROR(datagram) return self._datagramReceived(datagram)
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if datagram.mode not in ('netascii', 'octet'): return self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, "Unknown transfer mode %s, - expected " "'netascii' or 'octet' (case-insensitive)" % mode).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode)
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if datagram.mode not in ('netascii', 'octet'): return self.transport.write( ERRORDatagram.from_code( ERR_ILLEGAL_OP, "Unknown transfer mode %s, - expected " "'netascii' or 'octet' (case-insensitive)" % mode).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode)
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if mode not in (b'netascii', b'octet'): errmsg = ( u"Unknown transfer mode '%s', - expected 'netascii' or 'octet'" u"(case-insensitive)" % mode.decode("ascii")) return self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, errmsg.encode("ascii", "replace")).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode)
def test_error(self): # Zero-length payload self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, b'') # One byte payload self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, b'\x00') # Errorcode only (maybe this should fail) dgram = ERRORDatagram.from_wire(b'\x00\x01') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, errors[1]) # Errorcode with errstring - not terminated dgram = ERRORDatagram.from_wire(b'\x00\x01foobar') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, b'foobar') # Errorcode with errstring - terminated dgram = ERRORDatagram.from_wire(b'\x00\x01foobar\x00') self.assertEqual(dgram.errorcode, 1) self.assertEqual(dgram.errmsg, b'foobar') # Unknown errorcode self.assertRaises(WireProtocolError, ERRORDatagram.from_wire, b'\x00\x0efoobar') # Unknown errorcode in from_code self.assertRaises(WireProtocolError, ERRORDatagram.from_code, 13) # from_code with custom message dgram = ERRORDatagram.from_code( 3, b"I've accidentally the whole message") self.assertEqual(dgram.errorcode, 3) self.assertEqual(dgram.errmsg, b"I've accidentally the whole message") self.assertEqual( dgram.to_wire(), b"\x00\x05\x00\x03I've accidentally the whole message\x00") # from_code default message dgram = ERRORDatagram.from_code(3) self.assertEqual(dgram.errorcode, 3) self.assertEqual(dgram.errmsg, b"Disk full or allocation exceeded") self.assertEqual( dgram.to_wire(), b"\x00\x05\x00\x03Disk full or allocation exceeded\x00")
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) mode = datagram.mode.lower() if mode not in (b'netascii', b'octet'): errmsg = ( u"Unknown transfer mode '%s', - expected 'netascii' or 'octet'" u"(case-insensitive)" % mode.decode("ascii")) return self.transport.write( ERRORDatagram.from_code(ERR_ILLEGAL_OP, errmsg.encode("ascii", "replace")).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode)
def tftp_ACK(self, datagram): """Handle the incoming ACK TFTP datagram. @type datagram: L{ACKDatagram} """ if datagram.blocknum < self.blocknum: log.msg("Duplicate ACK for blocknum %s" % datagram.blocknum) elif datagram.blocknum == self.blocknum: self.timeout_watchdog.cancel() if self.completed: log.msg("Final ACK received, transfer successful") self.cancel() else: return self.nextBlock() else: self.transport.write(ERRORDatagram.from_code( ERR_ILLEGAL_OP, b"Block number mismatch").to_wire())
def datagramReceived(self, datagram, addr): datagram = TFTPDatagramFactory(*split_opcode(datagram)) log.msg("Datagram received from %s: %s" % (addr, datagram)) if datagram.opcode not in (OP_WRQ, OP_RRQ): log.msg( "Datagram with unexpected opcode %s was received without establishing " "the session. Ignoring." % datagram.opcode) return mode = datagram.mode.lower() if datagram.mode not in ('netascii', 'octet'): return self.transport.write( ERRORDatagram.from_code( ERR_ILLEGAL_OP, "Unknown transfer mode %s, - expected " "'netascii' or 'octet' (case-insensitive)" % mode).to_wire(), addr) self._clock.callLater(0, self._startSession, datagram, addr, mode)
def tftp_ACK(self, datagram): """Handle the incoming ACK TFTP datagram. @type datagram: L{ACKDatagram} """ if datagram.blocknum < self.blocknum: log.msg("Duplicate ACK for blocknum %s" % datagram.blocknum) elif datagram.blocknum == self.blocknum: self.timeout_watchdog.cancel() if self.completed: log.msg("Final ACK received, transfer successful") self.cancel() else: return self.nextBlock() else: self.transport.write( ERRORDatagram.from_code(ERR_ILLEGAL_OP, b"Block number mismatch").to_wire())
def _startSession(self, datagram, addr, mode): # Set up a call context so that we can pass extra arbitrary # information to interested backends without adding extra call # arguments, or switching to using a request object, for example. context = {} if self.transport is not None: # Add the local and remote addresses to the call context. local = self.transport.getHost() context["local"] = local.host, local.port context["remote"] = addr try: if datagram.opcode == OP_WRQ: fs_interface = yield call( context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call( context, self.backend.get_reader, datagram.filename) except Unsupported, e: self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr)
def _startSession(self, datagram, addr, mode): # Set up a call context so that we can pass extra arbitrary # information to interested backends without adding extra call # arguments, or switching to using a request object, for example. context = {} if self.transport is not None: # Add the local and remote addresses to the call context. local = self.transport.getHost() context["local"] = local.host, local.port context["remote"] = addr try: if datagram.opcode == OP_WRQ: fs_interface = yield call(context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call(context, self.backend.get_reader, datagram.filename) except Unsupported, e: self.transport.write( ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr)
def test_error_codes(self): # Error codes 0 to 8 are formally defined for TFTP (as of 2015-02-04). for errorcode in range(0, 9): data = struct.pack("!H", errorcode) + "message\x00" dgram = ERRORDatagram.from_wire(data) self.assertEqual(dgram.errorcode, errorcode)
def test_ERROR(self): err_dgram = ERRORDatagram.from_code(ERR_NOT_DEFINED, "no reason") yield self.rs.datagramReceived(err_dgram) self.failIf(self.transport.value()) self.failUnless(self.transport.disconnecting)
def readFailed(self, fail): """The reader reported an error. Notify the remote end and cancel the transfer""" log.err(fail) self.transport.write( ERRORDatagram.from_code(ERR_NOT_DEFINED, "Read failed").to_wire()) self.cancel()
def blockWriteFailure(self, failure): """Write failed""" log.err(failure) self.transport.write(ERRORDatagram.from_code(ERR_DISK_FULL).to_wire()) self.cancel()
def test_ERROR(self): err_dgram = ERRORDatagram.from_code(ERR_NOT_DEFINED, 'no reason') yield self.rs.datagramReceived(err_dgram) self.failIf(self.transport.value()) self.failUnless(self.transport.disconnecting)
def test_ERROR(self): err_dgram = ERRORDatagram.from_code(ERR_NOT_DEFINED, 'no reason') self.ws.datagramReceived(err_dgram) self.clock.advance(0.1) self.failIf(self.transport.value()) self.failUnless(self.transport.disconnecting)
def test_error_codes(self): # Error codes 0 to 8 are formally defined for TFTP (as of 2015-02-04). for errorcode in range(0, 9): data = struct.pack(b"!H", errorcode) + b"message\x00" dgram = ERRORDatagram.from_wire(data) self.assertEqual(dgram.errorcode, errorcode)
def readFailed(self, fail): """The reader reported an error. Notify the remote end and cancel the transfer""" log.err(fail) self.transport.write(ERRORDatagram.from_code(ERR_NOT_DEFINED, b"Read failed").to_wire()) self.cancel()
def test_ERROR(self): err_dgram = ERRORDatagram.from_code(ERR_NOT_DEFINED, b'no reason') yield self.rs.datagramReceived(err_dgram) self.assertFalse(self.transport.value()) self.assertTrue(self.transport.disconnecting)
if datagram.opcode == OP_WRQ: fs_interface = yield call(context, self.backend.get_writer, datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield call(context, self.backend.get_reader, datagram.filename) except Unsupported, e: self.transport.write( ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr) except AccessViolation: self.transport.write( ERRORDatagram.from_code(ERR_ACCESS_VIOLATION).to_wire(), addr) except FileExists: self.transport.write( ERRORDatagram.from_code(ERR_FILE_EXISTS).to_wire(), addr) except FileNotFound: self.transport.write( ERRORDatagram.from_code(ERR_FILE_NOT_FOUND).to_wire(), addr) except BackendError, e: self.transport.write( ERRORDatagram.from_code(ERR_NOT_DEFINED, str(e)).to_wire(), addr) else: if datagram.opcode == OP_WRQ: if mode == 'netascii': fs_interface = NetasciiReceiverProxy(fs_interface) session = RemoteOriginWriteSession(addr, fs_interface, datagram.options, _clock=self._clock)
self._clock.callLater(0, self._startSession, datagram, addr, mode) @inlineCallbacks def _startSession(self, datagram, addr, mode): try: if datagram.opcode == OP_WRQ: fs_interface = yield self.backend.get_writer(datagram.filename) elif datagram.opcode == OP_RRQ: fs_interface = yield self.backend.get_reader(datagram.filename) except Unsupported, e: self.transport.write(ERRORDatagram.from_code(ERR_ILLEGAL_OP, str(e)).to_wire(), addr) except AccessViolation: self.transport.write(ERRORDatagram.from_code(ERR_ACCESS_VIOLATION).to_wire(), addr) except FileExists: self.transport.write(ERRORDatagram.from_code(ERR_FILE_EXISTS).to_wire(), addr) except FileNotFound: self.transport.write(ERRORDatagram.from_code(ERR_FILE_NOT_FOUND).to_wire(), addr) except BackendError, e: self.transport.write(ERRORDatagram.from_code(ERR_NOT_DEFINED, str(e)).to_wire(), addr) else: if datagram.opcode == OP_WRQ: if mode == 'netascii': fs_interface = NetasciiReceiverProxy(fs_interface) session = RemoteOriginWriteSession(addr, fs_interface, datagram.options, _clock=self._clock) reactor.listenUDP(0, session) returnValue(session) elif datagram.opcode == OP_RRQ: if mode == 'netascii': fs_interface = NetasciiSenderProxy(fs_interface)