diff --git a/backend/src/services/emailProvider/pleskProvider.ts b/backend/src/services/emailProvider/pleskProvider.ts index 83c56f8c..bdb81dac 100644 --- a/backend/src/services/emailProvider/pleskProvider.ts +++ b/backend/src/services/emailProvider/pleskProvider.ts @@ -497,35 +497,62 @@ export class PleskEmailProvider implements IEmailProvider { }; } - // Plesk CLI API: Weiterleitungsziele aktualisieren. - // Format für -forwarding-addresses: "set:email1,email2" ersetzt alle Adressen. + // Plesk-CLI-Eigenheit: `-forwarding-addresses` akzeptiert NUR + // `add:` und `del:`, KEIN `set:`. Und `-forwarding` ist der + // Mailgroup-Schalter (Plesk nennt das im `--info` "Mailgroup", + // im CLI "forwarding" – derselbe Mechanismus, doppelt benannt). + // Es gibt KEINE separaten Mailgroup-Optionen wie `-mailgroup`. // - // WICHTIG: Mailgroup parallel deaktivieren. Plesk hat zwei - // unabhängige Verteil-Mechanismen (Mailgroup vs. Forwarding). - // Alt-Anlagen liefen oft via Mailgroup – unser `set:`-Befehl auf - // forwarding-addresses ändert dann eine ungenutzte Tabelle und - // Mails landen weiterhin bei den Mailgroup-Members. - // Der Service-Layer importiert vorher die Mailgroup-Members in die - // `targets`-Liste, damit beim Umschalten nichts verloren geht. - const cliParams = [ - '--update', email, - '-mailgroup', 'false', - '-forwarding', 'true', - '-forwarding-addresses', `set:${targets.join(',')}`, - ]; - console.log('[Plesk updateForwardTargets] CLI params:', cliParams); - const result = await this.request<{ code: number; stdout: string; stderr: string }>( - 'POST', - '/api/v2/cli/mail/call', - { params: cliParams }, - ); - console.log('[Plesk updateForwardTargets] response:', JSON.stringify(result, null, 2)); + // Wir bauen daher den Diff: alte Member abrufen, dann + // del: + add: in zwei separaten Calls. Idempotent, + // weil add: Duplikate ignoriert und del: nicht-vorhandene auch. + const currentMembers = exists.mailgroupMembers ?? []; + const targetsLower = new Set(targets.map((t) => t.toLowerCase())); + const currentLower = new Set(currentMembers.map((m) => m.toLowerCase())); + const toRemove = currentMembers.filter((m) => !targetsLower.has(m.toLowerCase())); + const toAdd = targets.filter((t) => !currentLower.has(t.toLowerCase())); - if (result.code !== 0 || /error|failed/i.test(result.stderr || '')) { - return { - success: false, - error: result.stderr?.trim() || result.stdout?.trim() || `Plesk returned code ${result.code}`, - }; + console.log( + `[Plesk updateForwardTargets] ${email} – aktuell: [${currentMembers.join(', ')}], ` + + `soll: [${targets.join(', ')}], entfernen: [${toRemove.join(', ')}], hinzufügen: [${toAdd.join(', ')}]`, + ); + + // Entfernen-Schritt + if (toRemove.length > 0) { + const delParams = [ + '--update', email, + '-forwarding-addresses', `del:${toRemove.join(',')}`, + ]; + const delResult = await this.request<{ code: number; stdout: string; stderr: string }>( + 'POST', '/api/v2/cli/mail/call', { params: delParams }, + ); + console.log('[Plesk updateForwardTargets] del response:', JSON.stringify(delResult, null, 2)); + if (delResult.code !== 0 || /error|failed/i.test(delResult.stderr || '')) { + return { + success: false, + error: delResult.stderr?.trim() || delResult.stdout?.trim() || `Plesk del returned code ${delResult.code}`, + }; + } + } + + // Hinzufügen-Schritt (impliziert -forwarding true, damit Mailgroup + // aktiviert bleibt bzw. wird). + if (toAdd.length > 0) { + const addParams = [ + '--update', email, + '-forwarding', 'true', + '-forwarding-addresses', `add:${toAdd.join(',')}`, + ]; + const addResult = await this.request<{ code: number; stdout: string; stderr: string }>( + 'POST', '/api/v2/cli/mail/call', { params: addParams }, + ); + console.log('[Plesk updateForwardTargets] add response:', JSON.stringify(addResult, null, 2)); + if (addResult.code !== 0 || /error|failed/i.test(addResult.stderr || '')) { + return { + success: false, + error: addResult.stderr?.trim() || addResult.stdout?.trim() || `Plesk add returned code ${addResult.code}`, + }; + } } return { diff --git a/docs/todo.md b/docs/todo.md index d6fa2bb8..a33ccf1a 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,23 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🐞 Plesk-Sync: `-forwarding-addresses set:` existiert gar nicht** + - Folge-Bug nach `a83358b`/`24e152b`: Sync verändert Plesk weiterhin + nicht. `plesk bin mail --help` zeigt: `-forwarding-addresses` + akzeptiert ausschließlich `add:` und `del:` – unser `set:` wurde + von Plesk silent verworfen. Außerdem gibt es keine separate + `-mailgroup`-Option; was Plesk im `--info` als `Mailgroup: true` + zeigt, ist genau das, was `-forwarding true` in der CLI setzt + (doppelt benannt). Mein vorheriges `-mailgroup false` lief auf + den Phantom-Parameter und triggerte `Unrecognized option`. + - `updateForwardTargets` baut jetzt den Diff: aktuelle Mailgroup- + Members (aus `emailExists`) gegen Soll-Liste; `del:` + + `add:` in zwei separaten CLI-Calls. Idempotent. + Case-insensitive – `Bruns.Gerhard` ≡ `bruns.gerhard`. + - Phantom-`-mailgroup`-Parameter entfernt. + - Smoke-Test gegen Prod-Stand (3 Bestands-Members + 1 neuer Eintrag): + nichts entfernt, nur `bzirks@gmx.de` hinzugefügt. + - [x] **🔒 Pentest 83.1-83.3: Auto-Import-Pfad härten** - **83.1 MEDIUM:** Auto-Import in `syncForwardingForEmail` umging `assertValidForwardingEmail`. Plesk-Member wie `attacker@plesk.internal`