Example #1
0
 def _process_request(self, req_data, req_addr):
     # The TFTP specification (RFC 1350) says that a request that is denied
     # should result in an error packet being sent. It also specifies that an
     # invalid packet received as part of a connection should result in an
     # error packet being sent. However, it does not specify what should
     # happen if an invalid packet is sent to the request port.
     # Most other implementations seem to ignore requests with an invalid
     # opcode, so we choose to do the same. We still log a debug message in
     # these cases.
     # A request must have at least two bytes for the opcode.
     if len(req_data) < 2:
         logger.debug('Invalid request from %s: The request was too short.',
                      socket_address_to_str(req_addr))
         return
     (opcode_num, ) = struct.unpack_from('!H', req_data)
     try:
         opcode = Opcode(opcode_num)
     except Exception:
         logger.debug(
             'Invalid request from %s: Opcode %s is not recognized.',
             socket_address_to_str(req_addr), opcode_num)
         return
     if opcode == Opcode.READ_REQUEST:
         self._process_read_request(req_data, req_addr)
     elif opcode == Opcode.WRITE_REQUEST:
         self._process_write_request(req_data, req_addr)
     else:
         self._process_invalid_request(opcode, req_addr)
Example #2
0
 def _process_write_request(self, req_data, req_addr):
     logger.error(
         'Received write request from %s, but this server only supports '
         'read requests', socket_address_to_str(req_addr))
     data = error_packet(ErrorCode.ACCESS_VIOLATION,
                         'Write requests are not allowed by this server.')
     self._socket.sendto(data, req_addr)
Example #3
0
 def _delegate_request(self):
     try:
         response_started = False
         # No sane HTTP client will ever send a request path that does
         # not start with a slash or contains a null byte. We do not
         # check that the request path does not contain a null byte after
         # decoding, this is the job of the handler that does the
         # decoding.
         if (not self.path.startswith('/')) or ('\0' in self.path):
             response_started = True
             self.send_error(http.HTTPStatus.BAD_REQUEST)
             return
         for handler in self.server.real_request_handlers:
             context = handler.prepare_context(self.path)
             if handler.can_handle(self.path, context):
                 try:
                     (status, headers, body) = handler.handle(
                         self.path,
                         self.command,
                         self.headers,
                         self.rfile,
                         self.client_address,
                         context)
                 except Exception:
                     logger.exception(
                         'Request handler for %s request for file "%s" '
                         'from client %s raised an exception.',
                         self.command,
                         self.path,
                         socket_address_to_str(self.client_address))
                     response_started = True
                     self.send_error(
                         http.HTTPStatus.INTERNAL_SERVER_ERROR)
                     return
                 # If status is an error code and there is neither a body
                 # nor are there headers, we use send_error instead of
                 # send_response.
                 response_started = True
                 if ((status.value >= 400)
                         and (not headers)
                         and (body is None)):
                     self.send_error(status)
                     return
                 self.send_response(status)
                 if headers is not None:
                     for header_name, header_value in headers.items():
                         self.send_header(header_name, header_value)
                         self.end_headers()
                 if body is not None:
                     shutil.copyfileobj(body, self.wfile)
                 return
         self.send_error(http.HTTPStatus.NOT_FOUND)
     except Exception:
         # We do not want a problem with a request to bubble up into the
         # calling code, so we log the problem and continue.
         logger.exception('Request processing failed.')
         if not response_started:
             self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR)
         return
Example #4
0
 def _process_invalid_request(self, opcode, req_addr):
     logger.debug(
         'Received request from %s with opcode %s, but only READ or WRITE '
         'requests are allowed on this server port.',
         socket_address_to_str(req_addr), opcode)
     data = error_packet(
         ErrorCode.ILLEGAL_OPERATION,
         'Only read or write requests are allowed on this port.')
     self._socket.sendto(data, req_addr)
Example #5
0
 def _handle_read(self, filename, transfer_mode, options, client_address,
                  handler_function, handler_context):
     # If debugging is enabled, we make the info message more verbose.
     if logger.isEnabledFor(logging.DEBUG):
         logger.info(
             'Received read request for file "%s" from client %s using mode '
             '%s and options %s.', filename,
             socket_address_to_str(client_address), transfer_mode, options)
     else:
         logger.info('Handling read request for file "%s" from client %s.',
                     filename, socket_address_to_str(client_address))
     # Constructing the request object is sufficient for handling the
     # request. The actual request handling is done by a daemon thread that
     # is created when constructing the object.
     _TftpReadRequest(filename, transfer_mode, options, client_address,
                      handler_function, handler_context,
                      self._default_timeout, self._max_timeout,
                      self._max_retries, self._max_block_size,
                      self._block_counter_wrap_value)
Example #6
0
 def _process_read_request(self, req_data, req_addr):
     try:
         (filename, transfer_mode, options) = decode_read_request(req_data)
     except Exception:
         # If we cannot decode the read request, this is an error, but in the
         # client, not the server, so we log it with a level of INFO.
         logger.info(
             'Decoding read request from %s resulted in an exception.',
             socket_address_to_str(req_addr))
         data = error_packet(ErrorCode.ILLEGAL_OPERATION,
                             'Malformed read request.')
         self._socket.sendto(data, req_addr)
         return
     # The mail transfer mode is only valid for write requests (and we do not
     # support it anyway).
     if transfer_mode == TransferMode.MAIL:
         # If the client requests transfer mode "mail" this is not an error
         # in the server, so we log it with a level of INFO.
         logger.info(
             'Read request from %s requested unsupported transfer mode '
             '"mail".', socket_address_to_str(req_addr))
         data = error_packet(
             ErrorCode.ILLEGAL_OPERATION,
             'Transfer mode mail is not allowed for read requests.')
         self._socket.sendto(data, req_addr)
         return
     # We try the request handlers in order until we find one that can handle
     # the request.
     for request_handler in self._request_handlers:
         handler_context = request_handler.prepare_context(filename)
         if request_handler.can_handle(filename, handler_context):
             self._handle_read(filename, transfer_mode, options, req_addr,
                               request_handler.handle, handler_context)
             return
     # If we cannot find such a request handler, we tell the client that the
     # file does not exist.
     logger.info(
         'No handler can fulfill request for file "%s" from client %s.',
         filename, socket_address_to_str(req_addr))
     data = error_packet(ErrorCode.FILE_NOT_FOUND,
                         'The requested file does not exist.')
     self._socket.sendto(data, req_addr)
Example #7
0
 def _receive(self):
     self._set_socket_timeout()
     (data, from_addr) = self._socket.recvfrom(MAX_REQUEST_PACKET_SIZE)
     # The TFTP specification demands that we send an error packet to each
     # client that sends a packet to a port that belongs to the connection
     # of a different client. For this connection, we are supposed to ignore
     # such a packet and continue waiting for a packet from the right client.
     while from_addr != self._client_address:
         logger.debug(
             'Received unexpected packet from %s on socket handling '
             'connection from %s.', socket_address_to_str(from_addr),
             socket_address_to_str(self._client_address))
         data = error_packet(
             ErrorCode.UNKNOWN_TRANSFER_ID,
             'This port is associated with a different client connection.')
         self._socket.sendto(data, from_addr)
         # Some time has already passed, so we have to reset the socket
         # timeout.
         self._set_socket_timeout()
         (data, from_addr) = self._socket.recvfrom(MAX_REQUEST_PACKET_SIZE)
     return data
Example #8
0
    def start(self):
        """
        Starts this server instance. This opens the server socket, binds it, and
        creates a deamon thread that processes requests.

        If the server is already running, this method does nothing.
        """
        with self._running_lock:
            if self._running:
                return
            self._socket = socket.socket(family=socket.AF_INET6,
                                         type=socket.SOCK_DGRAM)
            try:
                self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
                                        1)
                # This timeout specifies how quickly we can shutdown the server.
                self._socket.settimeout(0.1)
                # socket.IPPROTO_IPV6 is not available when running on Windows
                # and using Python < 3.8, so we fall back to a fixed value if it
                # is not available.
                try:
                    ipproto_ipv6 = socket.IPPROTO_IPV6
                except AttributeError:
                    ipproto_ipv6 = 41
                # socket.IPV6_V6ONLY, on the other hand, should be available on
                # Windows, at least for the Python versions we care about
                # (>= 3.5). If it is not available or if the call to setsockopt
                # fails, we log a warning, but continue.
                try:
                    self._socket.setsockopt(ipproto_ipv6, socket.IPV6_V6ONLY,
                                            0)
                except Exception:
                    logger.warning(
                        'Cannot set IPV6_V6ONLY socket option to 0, socket '
                        'might not be reachable via IPv4.')
                self._socket.bind((self._bind_address, self._bind_port))
                logger.info(
                    'TFTP server is listening on %s.',
                    socket_address_to_str(
                        (self._bind_address, self._bind_port)))
                self._main_thread = threading.Thread(target=self._run,
                                                     daemon=True)
                self._main_thread.start()
                self._running = True
            except BaseException:
                self._socket.close()
                self._socket = None
                raise
Example #9
0
    def start(self):
        """
        Start this server instance.

        If the server is already running, this method does nothing.
        """
        with self._running_lock:
            if self._running:
                return

            self._server = self._ThreadingHTTPServer(
                (self._bind_address, self._bind_port),
                self._DelegatingRequestHandler)
            logger.info(
                'HTTP server is listening on %s.',
                socket_address_to_str((self._bind_address, self._bind_port)))
            self._server.real_request_handlers = self._request_handlers
            self._main_thread = threading.Thread(target=self._run, daemon=True)
            self._main_thread.start()
            self._running = True
Example #10
0
 def _send_options_ack(self):
     data = options_ack_packet(self._options)
     tries_left = self._max_retries + 1
     ack_received = False
     while not ack_received and tries_left > 0:
         # With each try, we have to reset the timeout.
         self._reset_timeout()
         logger.debug('Sending OACK with options %s to %s.', self._options,
                      socket_address_to_str(self._client_address))
         self._send(data)
         try:
             while not ack_received:
                 block_number = self._receive_ack()
                 # The ACK for the options ACK has to use a block number of
                 # zero.
                 ack_received = (block_number == 0)
         except socket.timeout:
             if tries_left > 0:
                 tries_left -= 1
             else:
                 raise
Example #11
0
 def _send_data_block(self, block_number, data):
     packet_data = data_packet(block_number, data)
     tries_left = self._max_retries + 1
     ack_received = False
     while not ack_received and tries_left > 0:
         # With each try, we have to reset the timeout.
         self._reset_timeout()
         logger.debug(
             'Sending DATA with block # %s and %s bytes of data to %s.',
             block_number, len(data),
             socket_address_to_str(self._client_address))
         try:
             self._send(packet_data)
             while not ack_received:
                 ack_block_number = self._receive_ack()
                 # The block number in the ACK packet has to match the
                 # block number in the DATA packet.
                 ack_received = (ack_block_number == block_number)
         except socket.timeout:
             if tries_left > 0:
                 tries_left -= 1
             else:
                 raise
Example #12
0
 def _receive_ack(self):
     data = self._receive()
     if len(data) < 2:
         raise _TftpReadRequest._InvalidPacket('Short packet received.')
     (opcode_num, ) = struct.unpack_from('!H', data)
     try:
         opcode = Opcode(opcode_num)
     except ValueError:
         raise _TftpReadRequest._InvalidPacket(
             'Received packet with invalid opcode %s.', opcode_num)
     if opcode == Opcode.ACK:
         try:
             block_number = decode_ack(data)
             logger.debug('Received ACK for block # %s from %s.',
                          block_number,
                          socket_address_to_str(self._client_address))
             return block_number
         except ValueError:
             raise _TftpReadRequest._InvalidPacket(
                 'Received malformed ACK packet.')
     elif opcode == Opcode.ERROR:
         (error_code, error_message) = decode_error(data)
         if error_code == ErrorCode.TRANSFER_ABORTED:
             raise _TftpReadRequest._TransferAborted()
         if error_code is not None and error_message:
             raise _TftpReadRequest._ClientError(
                 'Error code {0}: {1}'.format(error_code.value,
                                              error_message))
         elif error_code is not None:
             raise _TftpReadRequest._ClientError('Error code {0}.'.format(
                 error_code.value))
         elif error_message:
             raise _TftpReadRequest._ClientError(
                 'Error code unknown: {0}'.format(error_message))
         else:
             raise _TftpReadRequest._ClientError('Unknown error.')
Example #13
0
 def _run(self):
     try:
         self._socket = socket.socket(family=socket.AF_INET6,
                                      type=socket.SOCK_DGRAM)
     except Exception:
         logger.exception(
             'Error creating socket for read request for file "%s" from '
             'client %s.', self._filename,
             socket_address_to_str(self._client_address))
         return
     # We want to make sure that we always close the socket, so we use it in
     # a with statement.
     with self._socket:
         self._socket.settimeout(self._timeout)
         # socket.IPPROTO_IPV6 is not available when running on Windows and
         # using Python < 3.8, so we fall back to a fixed value if it is not
         # available.
         try:
             ipproto_ipv6 = socket.IPPROTO_IPV6
         except AttributeError:
             ipproto_ipv6 = 41
         # socket.IPV6_V6ONLY, on the other hand, should be available on
         # Windows, at least for the Python versions we care about (>= 3.5).
         # If it is not available or if the call to setsockopt fails, we do
         # not even log a warning because we most likely already logged that
         # warning for the main socket.
         try:
             self._socket.setsockopt(ipproto_ipv6, socket.IPV6_V6ONLY, 0)
         except Exception:
             pass
         try:
             self._file = self._handler_function(self._filename,
                                                 self._client_address,
                                                 self._handler_context)
         except TftpError as e:
             # A TftpError is not necessarily a "real" error, so we only log
             # it with a level of info.
             logger.info(
                 'Request handler for read request for file "%s" from '
                 'client %s signalled an error with error code %s and '
                 'message "%s".', self._filename,
                 socket_address_to_str(self._client_address), e.error_code,
                 e.message)
             data = error_packet(e.error_code, e.message)
             # When sending the error, we want to use a fresh timeout value.
             self._socket.settimeout(self._timeout)
             self._send(data)
             return
         except Exception:
             logger.exception(
                 'Request handler for read request for file "%s" from '
                 'client %s raised an exception.', self._filename,
                 socket_address_to_str(self._client_address))
             data = error_packet(
                 ErrorCode.NOT_DEFINED,
                 'An internal error occurred while trying to fulfill the '
                 'request.')
             # When sending the error, we want to use a fresh timeout value.
             self._socket.settimeout(self._timeout)
             self._send(data)
             return
         # We always want to close the file-like object, so we use it in a
         # with statement.
         with self._file:
             # If the client requested the file size, we tell it if possible.
             # We do this here because we need access to the file in order to
             # determine its size.
             self._process_transfer_size_option()
             if self._netascii_mode:
                 self._file_reader = _TftpReadRequest._NetasciiReader(
                     self._file)
             else:
                 self._file_reader = _TftpReadRequest._OctetReader(
                     self._file)
             self._process_request()
Example #14
0
 def _process_request(self):
     try:
         if self._options:
             self._send_options_ack()
         self._send_data()
     except socket.timeout:
         # If we got a timeout the max. number of retries has been reached
         # and we give up.
         logger.info('Request for file "%s" from client %s timed out.',
                     self._filename,
                     socket_address_to_str(self._client_address))
     except _TftpReadRequest._BlockCounterOverflow:
         # This exception is raised if a file is so large that the limit of
         # the block counter is reached, but wrapping of the counter is not
         # allowed.
         logger.error(
             'Processing of request for file "%s" from client %s was '
             'aborted due the block counter reaching its limit.',
             self._filename, socket_address_to_str(self._client_address))
         # When sending the error, we want to use a fresh timeout value.
         self._socket.settimeout(self._timeout)
         self._send(
             error_packet(ErrorCode.NOT_DEFINED,
                          'File is too large to complete the transfer.'))
     except _TftpReadRequest._ClientError as e:
         # If the client sent an error message, we log it and abort this
         # connection.
         logger.info(
             'Processing of request for file "%s" from client %s was '
             'aborted due to a client error: %s', self._filename,
             socket_address_to_str(self._client_address), e.args[0])
     except _TftpReadRequest._InvalidPacket as e:
         message = e.args[0]
         # If we received an invalid packet, we log this, send an error to
         # the client, and abort this connection.
         logger.info(
             'Processing of request for file "%s" from client %s was '
             'aborted due to an invalid client packet: %s', self._filename,
             socket_address_to_str(self._client_address), message)
         # When sending the error, we want to use a fresh timeout value.
         self._socket.settimeout(self._timeout)
         self._send(error_packet(ErrorCode.NOT_DEFINED, message))
     except _TftpReadRequest._TransferAborted:
         # The client aborting a transfer is not an error, but we log a
         # simple informational message.
         logger.info(
             'Processing of request for file "%s" from client %s was '
             'aborted on client request.', self._filename,
             socket_address_to_str(self._client_address))
     except Exception:
         # Otherwise, there must be some kind of internal error, so we log
         # the exception and send an error code to the client.
         logger.exception(
             'Exception while processing read request for file "%s" from '
             'client %s.', self._filename,
             socket_address_to_str(self._client_address))
         data = error_packet(
             ErrorCode.NOT_DEFINED,
             'An internal error occurred while trying to fulfill the '
             'request.')
         # When sending the error, we want to use a fresh timeout value.
         self._socket.settimeout(self._timeout)
         self._send(data)