From 1de8fb98475c34f5c8592914d33a3244733b0db9 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 23 Apr 2026 15:05:04 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20IMAP/SMTP=20mit=20=C3=A4lteren=20TLS-Ver?= =?UTF-8?q?sionen=20zulassen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Fehler 'Client network socket disconnected before secure TLS connection was established' tritt auf, wenn der Mailserver nur alte TLS-Versionen (1.0/1.1) oder legacy Cipher-Suites anbietet - Node.js 20+ schließt dann den Socket, noch bevor überhaupt ein Zertifikat gesehen wird. Das Häkchen 'Selbstsignierte Zertifikate erlauben' greift zu spät, weil der Handshake gar nicht startet. Fix: Wenn 'Selbstsignierte Zertifikate erlauben' aktiv ist, setzen wir gleich auch minVersion='TLSv1' und ciphers='DEFAULT:@SECLEVEL=0'. Damit akzeptiert Node.js auch alte Cipher-Suites und TLS-Versionen des Mailservers. Bei aktivem 'allowSelfSignedCerts' heißt das zusammen: - rejectUnauthorized: false (Zertifikate akzeptieren auch wenn selbstsigniert) - minVersion: 'TLSv1' (auch alte TLS-Versionen zulassen) - ciphers: 'DEFAULT:@SECLEVEL=0' (auch schwache Ciphers zulassen) Refactor: - imapService: neuer Helper buildTlsOptions() – ersetzt 8 identische Inline-Setups, damit die Fix-Logik zentral gepflegt wird - smtpService: tls-Type erweitert (minVersion/ciphers), gleiche Logik Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/src/services/imapService.ts | 41 ++++++++++++++++++++++------- backend/src/services/smtpService.ts | 9 +++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/backend/src/services/imapService.ts b/backend/src/services/imapService.ts index 29fc4bc2..02dcbfee 100644 --- a/backend/src/services/imapService.ts +++ b/backend/src/services/imapService.ts @@ -16,6 +16,27 @@ export interface ImapCredentials { allowSelfSignedCerts?: boolean; // Selbstsignierte Zertifikate erlauben } +/** + * TLS-Optionen für IMAP-Verbindungen zusammenbauen. + * Wenn `allowSelfSignedCerts` aktiv ist, werden zusätzlich ältere TLS-Versionen + * (TLS 1.0+) und legacy Cipher-Suites erlaubt – hilfreich bei älteren Mailservern, + * die sonst den Socket sofort nach Connect schließen. + */ +function buildTlsOptions(credentials: ImapCredentials): Record | undefined { + const encryption = credentials.encryption ?? 'SSL'; + if (encryption === 'NONE') return undefined; + + const rejectUnauthorized = !credentials.allowSelfSignedCerts; + const options: Record = { rejectUnauthorized }; + + if (credentials.allowSelfSignedCerts) { + options.minVersion = 'TLSv1'; + options.ciphers = 'DEFAULT:@SECLEVEL=0'; + } + + return options; +} + export interface FetchedEmail { uid: number; messageId: string; @@ -106,7 +127,7 @@ export async function fetchEmails( // TLS-Optionen nur wenn nicht NONE if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } // Debug-Logging @@ -260,7 +281,7 @@ export async function testImapConnection(credentials: ImapCredentials): Promise< }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); @@ -325,7 +346,7 @@ export async function getHighestUid( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); @@ -414,12 +435,12 @@ async function fetchAttachmentInner( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); - console.log(`[fetchAttachment] Host: ${credentials.host}:${credentials.port} | User: ${credentials.user} | Folder: ${folder} | UID: ${uid} | File: ${attachmentFilename}`); + console.log(`[fetchAttachment] Host: ${credentials.host}:${credentials.port} | User: ${credentials.user} | Folder: ${folder} | UID: ${uid} | File: ${attachmentFilename} | AllowSelfSigned: ${credentials.allowSelfSignedCerts}`); try { await client.connect(); @@ -527,7 +548,7 @@ export async function appendToSent( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } console.log(`[IMAP] Appending email to ${sentFolder} folder...`); @@ -613,7 +634,7 @@ export async function fetchAttachmentList( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); @@ -705,7 +726,7 @@ export async function moveToTrash( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); @@ -778,7 +799,7 @@ export async function restoreFromTrash( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); @@ -849,7 +870,7 @@ export async function permanentDelete( }; if (encryption !== 'NONE') { - clientOptions.tls = { rejectUnauthorized }; + const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); diff --git a/backend/src/services/smtpService.ts b/backend/src/services/smtpService.ts index 9d4a8274..2330a3b0 100644 --- a/backend/src/services/smtpService.ts +++ b/backend/src/services/smtpService.ts @@ -69,7 +69,7 @@ export async function sendEmail( port: number; secure: boolean; auth: { user: string; pass: string }; - tls?: { rejectUnauthorized: boolean }; + tls?: { rejectUnauthorized: boolean; minVersion?: string; ciphers?: string }; ignoreTLS?: boolean; requireTLS?: boolean; connectionTimeout: number; @@ -91,6 +91,11 @@ export async function sendEmail( // TLS-Optionen nur wenn nicht NONE if (encryption !== 'NONE') { transportOptions.tls = { rejectUnauthorized }; + if (credentials.allowSelfSignedCerts) { + // Auch ältere TLS-Versionen + legacy Cipher-Suites für alte Server zulassen + transportOptions.tls.minVersion = 'TLSv1'; + transportOptions.tls.ciphers = 'DEFAULT:@SECLEVEL=0'; + } } else { // Keine Verschlüsselung: STARTTLS ignorieren transportOptions.ignoreTLS = true; @@ -273,7 +278,7 @@ export async function testSmtpConnection(credentials: SmtpCredentials): Promise< port: number; secure: boolean; auth: { user: string; pass: string }; - tls?: { rejectUnauthorized: boolean }; + tls?: { rejectUnauthorized: boolean; minVersion?: string; ciphers?: string }; ignoreTLS?: boolean; connectionTimeout: number; greetingTimeout: number;