diff --git a/.gitignore b/.gitignore index 8b07141..aa30222 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ pyrightconfig.json # Tests .coverage + +# Editor +.vscode \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 985883d..15191a4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file. ### Changed +#### 2025-12-19 + +* `./pysieved/managesieve.py`: Added error handling for client disconnects during read/write. + #### 2026-01-13 * `./tests/base.py`: Added close method for `MockClient` to properly shutdown the client. diff --git a/build_deb.sh b/build_deb.sh index de4d9f2..c694c41 100755 --- a/build_deb.sh +++ b/build_deb.sh @@ -1,6 +1,6 @@ #!/bin/bash -export BUILD_VERSION=${BUILD_VERSION:-0.2.6} +export BUILD_VERSION=${BUILD_VERSION:-0.2.7} docker build -t pysieved-deb-builder . docker run --name pysieved-builder pysieved-deb-builder bash -c " diff --git a/debian/changelog b/debian/changelog index 76cc10b..8e4a923 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +pysieved (0.2.7) jammy; urgency=medium + + * Added error handling for client disconnects during read/write. + + -- Team noris SKG-PRJ Fri, 19 DEC 2025 18:00:00 +0200 + pysieved (0.2.6) jammy; urgency=medium * Replaced syslog with File Logging diff --git a/pysieved/managesieve.py b/pysieved/managesieve.py index 00e8d2b..ebd0e49 100755 --- a/pysieved/managesieve.py +++ b/pysieved/managesieve.py @@ -18,9 +18,10 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ## USA # -# 26 November 2025 - Modified by F. Ioannidis. +# 19 December 2025 - Modified by A. Manalikadis. +import errno import socketserver as SocketServer import sys import time @@ -168,22 +169,41 @@ def readline(self) -> str: def handle(self): def _write(content: str | bytes): - message = content + try: + message = content - if isinstance(content, str): - message = content.encode() + if isinstance(content, str): + message = content.encode() - return self.request.send(message) + return self.request.send(message) + + except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError): + # Client closed early (common on LOGOUT / timeouts / reconnects) + raise Hangup() + except OSError as e: + if e.errno in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED, errno.ETIMEDOUT): + raise Hangup() + raise def _read(n: int) -> str: """Read `n` bytes from socket and convert to string.""" + try: + received = self.request.recv(n) + + if not received: + raise Hangup() - received = self.request.recv(n) + if isinstance(received, bytes): + received = received.decode() - if isinstance(received, bytes): - received = received.decode() + return received + except (ConnectionResetError, ConnectionAbortedError): + raise Hangup() + except OSError as e: + if e.errno in (errno.ECONNRESET, errno.ECONNABORTED, errno.ETIMEDOUT): + raise Hangup() + raise - return received self.buf = b"" self.write = _write @@ -191,9 +211,8 @@ def _read(n: int) -> str: self.log(1, "Connect from %r" % (self.client_address,)) - self.do_capability() - try: + self.do_capability() while True: try: cmd = self.get_command() @@ -220,10 +239,13 @@ def _read(n: int) -> str: except Hangup: pass - except: + except Exception: _, t, v, tbinfo = compact_traceback() self.log(-1, "[ERROR] %s:%s %s" % (t, v, tbinfo)) - self.bye(reason="Server error") + try: + self.bye(reason="Server error") + except Exception: + pass raise def finish(self): diff --git a/setup.py b/setup.py index 480c780..d91ddd5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="pysieved", description="Core daemon for the pysieved project", - version=os.getenv("BUILD_VERSION", "0.2.6"), + version=os.getenv("BUILD_VERSION", "0.2.7"), packages=find_packages(where="."), entry_points={ "console_scripts": [