113 lines
3 KiB
Python
113 lines
3 KiB
Python
|
# JTFTP - Python/AsyncIO TFTP Server
|
||
|
# Copyright (C) 2022 Jeffrey C. Ollie
|
||
|
#
|
||
|
# This program is free software: you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation, either version 3 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
|
||
|
|
||
|
class TFTPError(Exception):
|
||
|
"""Base exception class for this package"""
|
||
|
|
||
|
|
||
|
class WireProtocolError(TFTPError):
|
||
|
"""Base exception class for wire-protocol level errors"""
|
||
|
|
||
|
|
||
|
class InvalidOpcodeError(WireProtocolError):
|
||
|
"""An invalid opcode was encountered"""
|
||
|
|
||
|
opcode: int
|
||
|
|
||
|
def __init__(self, opcode: int):
|
||
|
self.opcode = opcode
|
||
|
super().__init__(f"Invalid opcode: {opcode}")
|
||
|
|
||
|
|
||
|
class PayloadDecodeError(WireProtocolError):
|
||
|
"""Failed to parse the payload"""
|
||
|
|
||
|
|
||
|
class OptionsDecodeError(PayloadDecodeError):
|
||
|
"""Failed to parse options in the WRQ/RRQ datagram. It is distinct from
|
||
|
L{PayloadDecodeError} so that it can be caught and dealt with gracefully
|
||
|
(pretend we didn't see any options at all, perhaps).
|
||
|
|
||
|
"""
|
||
|
|
||
|
|
||
|
class InvalidErrorcodeError(PayloadDecodeError):
|
||
|
"""An ERROR datagram has an error code, that does not correspond to any known
|
||
|
error code values.
|
||
|
|
||
|
@ivar errorcode: The error code, that we were unable to parse
|
||
|
@type errorcode: C{int}
|
||
|
|
||
|
"""
|
||
|
|
||
|
error_code: int
|
||
|
|
||
|
def __init__(self, error_code: int):
|
||
|
self.error_code = error_code
|
||
|
super().__init__(f"unknown error code: {error_code}")
|
||
|
|
||
|
|
||
|
class BackendError(TFTPError):
|
||
|
"""Base exception class for backend errors"""
|
||
|
|
||
|
|
||
|
class Unsupported(BackendError):
|
||
|
"""Requested operation (read/write) is not supported"""
|
||
|
|
||
|
|
||
|
class AccessViolation(BackendError):
|
||
|
"""Illegal filesystem operation. Corresponds to the "(2) Access violation"
|
||
|
TFTP error code.
|
||
|
|
||
|
One of the prime examples of these is an attempt at directory traversal.
|
||
|
|
||
|
"""
|
||
|
|
||
|
|
||
|
class FileNotFound(BackendError):
|
||
|
"""File not found.
|
||
|
|
||
|
Corresponds to the "(1) File not found" TFTP error code.
|
||
|
|
||
|
@ivar file_path: Path to the file, that was requested
|
||
|
@type file_path: C{bytes} or L{twisted.python.filepath.FilePath}
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, file_path):
|
||
|
self.file_path = file_path
|
||
|
|
||
|
def __str__(self):
|
||
|
return f"File not found: {self.file_path}"
|
||
|
|
||
|
|
||
|
class FileExists(BackendError):
|
||
|
"""File exists.
|
||
|
|
||
|
Corresponds to the "(6) File already exists" TFTP error code.
|
||
|
|
||
|
@ivar file_path: Path to file
|
||
|
@type file_path: C{bytes} or L{twisted.python.filepath.FilePath}
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, file_path):
|
||
|
self.file_path = file_path
|
||
|
|
||
|
def __str__(self):
|
||
|
return f"File already exists: {self.file_path}"
|