Plesk-Sync: Legacy-Mailgroup-Adressen synchronisierten nicht
Prod-Bug: zusätzliche Weiterleitung eintragen → Toast meldet Erfolg, Plesk übernimmt nichts. Plesk hat zwei unabhängige Verteil-Mechanismen, Mailgroup (alte CLI-Anlagen) und Forwarding (neue). Unser Sync schrieb nur in Forwarding, die alte Adresse lief aber via Mailgroup → set:-Befehle landeten in ungenutzter Tabelle. Stage funktionierte, weil dort frisch im Forwarding- Modus angelegt. - EmailExistsResult um mailgroupActive/Members + forwardingActive/ Targets erweitert. - pleskProvider.emailExists parst alle vier Felder aus --info- stdout (Mailgroup: true|false, Group member(s): ..., Forward request: ...). - pleskProvider.updateForwardTargets setzt -mailgroup false dazu – deaktiviert den Legacy-Mechanismus. - syncForwardingForEmail holt vorm Plesk-Update die bestehenden Mailgroup-Members und Forwarding-Targets ab und importiert sie in unsere additionalForwardingEmails-Liste (canonical-Key-Dedup). Verlustfrei – kein Empfänger fällt beim Umschalten raus. Smoke-Test mit echtem Plesk-stdout (User-Log): 3 Group-Members sauber geparst, leeres "Forward request" als [] erkannt.
This commit is contained in:
@@ -180,17 +180,56 @@ export class PleskEmailProvider implements IEmailProvider {
|
||||
|
||||
// Mailbox-Status aus stdout parsen (Format: "Mailbox: true" oder "Mailbox: false")
|
||||
let hasMailbox: boolean | undefined;
|
||||
let mailgroupActive: boolean | undefined;
|
||||
let mailgroupMembers: string[] | undefined;
|
||||
let forwardingActive: boolean | undefined;
|
||||
let forwardingTargets: string[] | undefined;
|
||||
if (exists && result.stdout) {
|
||||
const mailboxMatch = result.stdout.match(/Mailbox:\s*(true|false)/i);
|
||||
if (mailboxMatch) {
|
||||
hasMailbox = mailboxMatch[1].toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
// Mailgroup-Status + Mitglieder. Plesk listet sie auf einer
|
||||
// Zeile, Adressen sind durch Whitespace getrennt.
|
||||
const mailgroupMatch = result.stdout.match(/Mailgroup:\s*(true|false)/i);
|
||||
if (mailgroupMatch) {
|
||||
mailgroupActive = mailgroupMatch[1].toLowerCase() === 'true';
|
||||
}
|
||||
const groupMembersMatch = result.stdout.match(/Group member\(s\):\s*([^\n]*)/i);
|
||||
if (groupMembersMatch) {
|
||||
mailgroupMembers = groupMembersMatch[1]
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter((m) => m.includes('@'));
|
||||
}
|
||||
|
||||
// Forwarding-Status + Ziele. Plesk druckt "Forward request: <addrs>".
|
||||
// Auf manchen Plesk-Versionen heißt das Feld auch "Forwarding".
|
||||
const forwardActiveMatch = result.stdout.match(/Forwarding:\s*(true|false)/i);
|
||||
if (forwardActiveMatch) {
|
||||
forwardingActive = forwardActiveMatch[1].toLowerCase() === 'true';
|
||||
}
|
||||
const forwardTargetsMatch = result.stdout.match(/Forward(?:ing)?(?: request)?:\s*([^\n]*)/i);
|
||||
if (forwardTargetsMatch) {
|
||||
forwardingTargets = forwardTargetsMatch[1]
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter((m) => m.includes('@'));
|
||||
if (forwardingActive === undefined) {
|
||||
forwardingActive = (forwardingTargets?.length ?? 0) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
exists,
|
||||
email: exists ? email : undefined,
|
||||
hasMailbox,
|
||||
mailgroupActive,
|
||||
mailgroupMembers,
|
||||
forwardingActive,
|
||||
forwardingTargets,
|
||||
};
|
||||
} catch (error) {
|
||||
// HTTP-Fehler oder Netzwerkfehler
|
||||
@@ -458,11 +497,20 @@ export class PleskEmailProvider implements IEmailProvider {
|
||||
};
|
||||
}
|
||||
|
||||
// Plesk CLI API: Weiterleitungsziele aktualisieren
|
||||
// Format für -forwarding-addresses: "set:email1,email2" ersetzt alle Adressen
|
||||
// Plesk CLI API: Weiterleitungsziele aktualisieren.
|
||||
// Format für -forwarding-addresses: "set:email1,email2" ersetzt alle Adressen.
|
||||
//
|
||||
// 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.
|
||||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||||
params: [
|
||||
'--update', email,
|
||||
'-mailgroup', 'false',
|
||||
'-forwarding', 'true',
|
||||
'-forwarding-addresses', `set:${targets.join(',')}`,
|
||||
],
|
||||
|
||||
@@ -42,6 +42,14 @@ export interface EmailExistsResult {
|
||||
exists: boolean;
|
||||
email?: string;
|
||||
hasMailbox?: boolean; // true wenn echte Mailbox vorhanden
|
||||
// Plesk hat zwei unabhängige Verteil-Mechanismen, beide können parallel
|
||||
// aktiv sein. Manuelle/Legacy-Anlagen nutzen oft "Mailgroup" statt
|
||||
// "Forwarding" – unser Sync muss alte Mitglieder dort einsammeln,
|
||||
// sonst gehen sie beim Umschalten auf Forwarding verloren.
|
||||
mailgroupActive?: boolean;
|
||||
mailgroupMembers?: string[];
|
||||
forwardingActive?: boolean;
|
||||
forwardingTargets?: string[];
|
||||
}
|
||||
|
||||
export interface EmailOperationResult {
|
||||
|
||||
@@ -558,7 +558,50 @@ export async function syncForwardingForEmail(
|
||||
|
||||
const localPart = stressfreiEmail.email.split('@')[0];
|
||||
|
||||
// 1) Forwards neu setzen.
|
||||
// 0) Auto-Migration: Plesk hat zwei Verteil-Mechanismen (Mailgroup +
|
||||
// Forwarding). Alt-Anlagen liefen oft via Mailgroup – unser Sync
|
||||
// schreibt aber nur in die Forwarding-Liste, daher landeten neue
|
||||
// Adressen nirgendwo. Hier holen wir die aktuellen Mailgroup-Members
|
||||
// ab und ziehen alle, die wir nicht schon kennen, in unsere
|
||||
// additionalForwardingEmails-Liste rein. Der nachfolgende Plesk-Call
|
||||
// deaktiviert dann die Mailgroup und schreibt die volle Liste als
|
||||
// Forwarding. Verlustfrei – kein Empfänger fällt raus.
|
||||
try {
|
||||
const pleskState = await checkEmailExists(localPart);
|
||||
const existingMembers = [
|
||||
...(pleskState.mailgroupMembers ?? []),
|
||||
...(pleskState.forwardingTargets ?? []),
|
||||
];
|
||||
const newImports: string[] = [];
|
||||
for (const member of existingMembers) {
|
||||
const key = canonicalEmailKey(member);
|
||||
if (!seenKeys.has(key)) {
|
||||
seenKeys.add(key);
|
||||
forwardTargets.push(member);
|
||||
newImports.push(member);
|
||||
}
|
||||
}
|
||||
if (newImports.length > 0) {
|
||||
const mergedAdditional = [
|
||||
...parseAdditionalForwards(stressfreiEmail.additionalForwardingEmails),
|
||||
...newImports,
|
||||
];
|
||||
await prisma.stressfreiEmail.update({
|
||||
where: { id },
|
||||
data: { additionalForwardingEmails: serializeAdditionalForwards(mergedAdditional) },
|
||||
});
|
||||
console.log(
|
||||
`[syncForwardingForEmail] Importiert aus Plesk-Mailgroup für ${stressfreiEmail.email}:`,
|
||||
newImports,
|
||||
);
|
||||
}
|
||||
} catch (importErr) {
|
||||
// Nicht hart fehlschlagen – im schlimmsten Fall fehlen ein paar
|
||||
// alte Empfänger, aber der eigentliche Sync soll trotzdem laufen.
|
||||
console.error('[syncForwardingForEmail] Mailgroup-Import fehlgeschlagen:', importErr);
|
||||
}
|
||||
|
||||
// 1) Forwards neu setzen (deaktiviert intern Mailgroup).
|
||||
const forwardResult = await setEmailForwardTargets(localPart, forwardTargets);
|
||||
if (!forwardResult.success) {
|
||||
// Wenn Plesk meldet „nicht gefunden", liefern wir eine sprechende Meldung
|
||||
|
||||
@@ -97,6 +97,30 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🐞 Plesk-Sync: Legacy-Mailgroup-Adressen synchronisierten nicht**
|
||||
- Prod-Bug: User trägt zusätzliche Weiterleitung ein, Toast meldet
|
||||
Erfolg, aber Plesk übernimmt nichts. Ursache: Plesk hat zwei
|
||||
Verteil-Mechanismen, **Mailgroup** (alte CLI-Anlagen,
|
||||
`Group member(s):`) und **Forwarding** (`Forward request:`). Unser
|
||||
Sync schrieb nur in Forwarding, die Adresse lief aber via Mailgroup
|
||||
→ unsere `set:`-Befehle landeten in einer ungenutzten Tabelle.
|
||||
Stage funktionierte, weil dort die Adressen frisch vom CRM angelegt
|
||||
wurden (Forwarding-Modus von Anfang an).
|
||||
- `EmailExistsResult` um `mailgroupActive` + `mailgroupMembers` +
|
||||
`forwardingActive` + `forwardingTargets` erweitert.
|
||||
- `pleskProvider.emailExists` parst alle vier Felder aus dem
|
||||
`--info`-stdout (`Mailgroup: true|false`, `Group member(s): ...`,
|
||||
`Forward request: ...`).
|
||||
- `pleskProvider.updateForwardTargets` setzt jetzt zusätzlich
|
||||
`-mailgroup false`, damit der Legacy-Mechanismus deaktiviert wird
|
||||
und nur noch Forwarding aktiv ist.
|
||||
- `syncForwardingForEmail`: vor dem Plesk-Update werden bestehende
|
||||
Mailgroup-Members + Forwarding-Targets abgeholt und in unsere
|
||||
`additionalForwardingEmails`-Liste **importiert** (canonical-Key-
|
||||
Dedup). Verlustfrei – kein bestehender Empfänger fällt beim
|
||||
Umschalten auf Forwarding raus. Import-Fehler werden geloggt,
|
||||
aber der eigentliche Sync läuft trotzdem.
|
||||
|
||||
- [x] **🔒 Pentest 81.1 (MEDIUM): Self-Forward erzeugte Mail-Loop am Provider**
|
||||
- Bug: User konnte die Stressfrei-Adresse selbst (z.B.
|
||||
`max.mustermann@stressfrei-wechseln.net`) als zusätzliches
|
||||
|
||||
Reference in New Issue
Block a user