@@ -37,14 +37,14 @@ public void ConfirmSingleReturnPathAfterAccountForward()
3737 // Wait for the auto-reply.
3838 var text = Pop3ClientSimulator . AssertGetFirstMessageText ( account2 . Address , "test" ) ;
3939
40- Assert . IsFalse ( text . Contains ( "Return-Path: account2@example.test" ) ) ;
41- Assert . IsFalse ( text . Contains ( "Return-Path: account1@example.test" ) ) ;
42- Assert . IsTrue ( text . Contains ( "Return-Path: original-address@example.test" ) ) ;
40+ Assert . IsFalse ( text . Contains ( "Return-Path: < account2@example.test> " ) ) ;
41+ Assert . IsFalse ( text . Contains ( "Return-Path: < account1@example.test> " ) ) ;
42+ Assert . IsTrue ( text . Contains ( "Return-Path: < original-address@example.test> " ) ) ;
4343 }
4444
4545 [ Test ]
4646 [ Category ( "Accounts" ) ]
47- [ Description ( "Ensure that messges aren't forwarded if they re deleted using a rule." ) ]
47+ [ Description ( "Ensure that messages aren't forwarded if they re deleted using a rule." ) ]
4848 public void ConfirmSingleReturnPathAfterRuleForward ( )
4949 {
5050 // Create a test account
@@ -81,9 +81,9 @@ public void ConfirmSingleReturnPathAfterRuleForward()
8181 // Wait for the auto-reply.
8282 var text = Pop3ClientSimulator . AssertGetFirstMessageText ( account2 . Address , "test" ) ;
8383
84- Assert . IsFalse ( text . Contains ( "Return-Path: account-a@example.test" ) ) ;
85- Assert . IsFalse ( text . Contains ( "Return-Path: account2@example.test" ) ) ;
86- Assert . IsTrue ( text . Contains ( "Return-Path: external@example.test" ) ) ;
84+ Assert . IsFalse ( text . Contains ( "Return-Path: < account-a@example.test> " ) ) ;
85+ Assert . IsFalse ( text . Contains ( "Return-Path: < account2@example.test> " ) ) ;
86+ Assert . IsTrue ( text . Contains ( "Return-Path: < external@example.test> " ) ) ;
8787 }
8888
8989 [ Test ]
@@ -136,6 +136,8 @@ public void TestAutoReply()
136136 var s = pop3ClientSimulator . GetFirstMessageText ( account1 . Address , "test" ) ;
137137 if ( s . IndexOf ( "Out of office!" ) < 0 )
138138 throw new Exception ( "ERROR - Auto reply subject not set properly." ) ;
139+ Assert . IsTrue ( s . Contains ( "Return-Path: <>" ) ,
140+ "Vacation reply envelope sender must be empty (<>) to prevent mail loops per RFC 3834." ) ;
139141
140142 account2 . VacationMessageIsOn = false ;
141143 account2 . Save ( ) ;
@@ -318,7 +320,73 @@ public void WhenForwardingFromAddressShouldBeSetToForwardingAccount()
318320 var message = Pop3ClientSimulator . AssertGetFirstMessageText ( list . Address , "test" ) ;
319321
320322
321- Assert . IsTrue ( message . Contains ( "Return-Path: sender@example.test" ) ) ;
323+ Assert . IsTrue ( message . Contains ( "Return-Path: <sender@example.test>" ) ) ;
324+ }
325+
326+ [ Test ]
327+ [ Category ( "Accounts" ) ]
328+ [ Description ( "When forwarding a bounce (MAIL FROM:<>), the null envelope-from must be preserved so the forwarded copy cannot itself generate a bounce loop." ) ]
329+ public void WhenAccountForwardingBounceMessageShouldPreserveNullEnvelopeFrom ( )
330+ {
331+ var forwarder = SingletonProvider < TestSetup > . Instance . AddAccount ( _domain , "forwarder@example.test" , "test" ) ;
332+ var recipient = SingletonProvider < TestSetup > . Instance . AddAccount ( _domain , "recipient@example.test" , "test" ) ;
333+
334+ forwarder . ForwardEnabled = true ;
335+ forwarder . ForwardAddress = recipient . Address ;
336+ forwarder . ForwardKeepOriginal = true ;
337+ forwarder . Save ( ) ;
338+
339+ // Send with empty envelope-from (MAIL FROM:<>), simulating a bounce/DSN.
340+ var smtp = new SmtpClientSimulator ( ) ;
341+ smtp . Send ( "" , new System . Collections . Generic . List < string > { forwarder . Address } , "Bounce subject" , "Bounce body" ) ;
342+
343+ Pop3ClientSimulator . AssertMessageCount ( forwarder . Address , "test" , 1 ) ;
344+
345+ _application . SubmitEMail ( ) ;
346+ CustomAsserts . AssertRecipientsInDeliveryQueue ( 0 ) ;
347+
348+ var message = Pop3ClientSimulator . AssertGetFirstMessageText ( recipient . Address , "test" ) ;
349+ Assert . IsTrue ( message . Contains ( "Return-Path: <>" ) ,
350+ "Forwarding a bounce must preserve the null envelope-from to prevent bounce loops." ) ;
351+ }
352+
353+ [ Test ]
354+ [ Category ( "Accounts" ) ]
355+ [ Description ( "When a rule forwards a bounce (MAIL FROM:<>), the null envelope-from must be preserved so the forwarded copy cannot itself generate a bounce loop." ) ]
356+ public void WhenRuleForwardsBounceMessageShouldPreserveNullEnvelopeFrom ( )
357+ {
358+ var account1 = SingletonProvider < TestSetup > . Instance . AddAccount ( _domain , "rulefwd-src@example.test" , "test" ) ;
359+ var account2 = SingletonProvider < TestSetup > . Instance . AddAccount ( _domain , "rulefwd-dst@example.test" , "test" ) ;
360+
361+ var rule = account1 . Rules . Add ( ) ;
362+ rule . Name = "Forward all" ;
363+ rule . Active = true ;
364+
365+ var criteria = rule . Criterias . Add ( ) ;
366+ criteria . UsePredefined = true ;
367+ criteria . PredefinedField = eRulePredefinedField . eFTMessageSize ;
368+ criteria . MatchType = eRuleMatchType . eMTGreaterThan ;
369+ criteria . MatchValue = "0" ;
370+ criteria . Save ( ) ;
371+
372+ var action = rule . Actions . Add ( ) ;
373+ action . Type = eRuleActionType . eRAForwardEmail ;
374+ action . To = account2 . Address ;
375+ action . Save ( ) ;
376+
377+ rule . Save ( ) ;
378+
379+ // Send with empty envelope-from (MAIL FROM:<>), simulating a bounce/DSN.
380+ var smtp = new SmtpClientSimulator ( ) ;
381+ smtp . Send ( "" , new System . Collections . Generic . List < string > { account1 . Address } , "Bounce subject" , "Bounce body" ) ;
382+
383+ Pop3ClientSimulator . AssertMessageCount ( account1 . Address , "test" , 1 ) ;
384+ _application . SubmitEMail ( ) ;
385+ CustomAsserts . AssertRecipientsInDeliveryQueue ( 0 ) ;
386+
387+ var message = Pop3ClientSimulator . AssertGetFirstMessageText ( account2 . Address , "test" ) ;
388+ Assert . IsTrue ( message . Contains ( "Return-Path: <>" ) ,
389+ "Rule-based forwarding of a bounce must preserve the null envelope-from to prevent bounce loops." ) ;
322390 }
323391
324392 [ Test ]
0 commit comments