From cbdd296aae6f99d69c3aed58d4546aca65f46e14 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 31 May 2026 14:03:07 +0200 Subject: [PATCH] Add network timeouts to prevent container hangs on unreachable servers Without timeouts, smtplib.SMTP() / imaplib.IMAP4_SSL() / paramiko.Transport() hang indefinitely when the remote server is down or firewall-dropped, blocking the entire background thread and eventually freezing the app. - SMTP: 30s connect/operation timeout - IMAP: 30s connect/operation timeout - SFTP (paramiko): 15s socket connect, 15s banner, 30s auth - SMB and FTP already had timeouts Co-Authored-By: Claude Opus 4.6 (1M context) --- app/ftp_processor.py | 12 +++++++++++- app/mail_processor.py | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/ftp_processor.py b/app/ftp_processor.py index e4be213..3424f55 100644 --- a/app/ftp_processor.py +++ b/app/ftp_processor.py @@ -197,9 +197,19 @@ class _PlainFtpAdapter(_FtpAdapter): # SFTP (paramiko) # --------------------------------------------------------------------------- +SFTP_CONNECT_TIMEOUT = 15 # seconds +SFTP_BANNER_TIMEOUT = 15 +SFTP_AUTH_TIMEOUT = 30 + + class _SftpAdapter(_FtpAdapter): def __init__(self, server: str, port: int, username: str, password: str): - self.transport = paramiko.Transport((server, port)) + # Build socket with timeout to prevent hangs on unreachable hosts + import socket as _socket + sock = _socket.create_connection((server, port), timeout=SFTP_CONNECT_TIMEOUT) + self.transport = paramiko.Transport(sock) + self.transport.banner_timeout = SFTP_BANNER_TIMEOUT + self.transport.auth_timeout = SFTP_AUTH_TIMEOUT self.transport.connect(username=username, password=password) self.sftp = paramiko.SFTPClient.from_transport(self.transport) diff --git a/app/mail_processor.py b/app/mail_processor.py index 5aa55a3..7338e73 100644 --- a/app/mail_processor.py +++ b/app/mail_processor.py @@ -40,14 +40,18 @@ def _connect_imap(settings: dict) -> imaplib.IMAP4_SSL | imaplib.IMAP4: if use_ssl: ctx = ssl.create_default_context() - conn = imaplib.IMAP4_SSL(server, port, ssl_context=ctx) + conn = imaplib.IMAP4_SSL(server, port, ssl_context=ctx, timeout=IMAP_TIMEOUT) else: - conn = imaplib.IMAP4(server, port) + conn = imaplib.IMAP4(server, port, timeout=IMAP_TIMEOUT) conn.login(settings["imap_username"], settings["imap_password"]) return conn +SMTP_TIMEOUT = 30 # seconds - prevents hangs when SMTP server is unreachable +IMAP_TIMEOUT = 30 # seconds + + def _connect_smtp(settings: dict) -> smtplib.SMTP | smtplib.SMTP_SSL: server = settings["smtp_server"] port = int(settings.get("smtp_port", 587)) @@ -55,9 +59,9 @@ def _connect_smtp(settings: dict) -> smtplib.SMTP | smtplib.SMTP_SSL: if mode == "ssl": ctx = ssl.create_default_context() - conn = smtplib.SMTP_SSL(server, port, context=ctx) + conn = smtplib.SMTP_SSL(server, port, context=ctx, timeout=SMTP_TIMEOUT) else: - conn = smtplib.SMTP(server, port) + conn = smtplib.SMTP(server, port, timeout=SMTP_TIMEOUT) if mode == "starttls": ctx = ssl.create_default_context() conn.starttls(context=ctx)