-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug fixes and added more tests #4
Conversation
Update empty_return_path to match default in source code.
Moved reject_all=false from [check] to [reject] and renamed to all_bounces=false
Moved reject_all from [check] to [reject] and renamed to all_bounces
Added Sinon to create mocks for testing
- Updated bounce_spf() to use Promises. - Moved bad_rcpt function from hook_data to hook_rcpt_ok. - Renamed config key check.reject_all to reject.all_bounces. - Moved config key check.bad_rcpt to reject.bad_rcpt - Fixed bug where config key reject.empty_return_path was ignored. - Fixed timeout bug in bounce_spf() - Modified Message-ID header regex to check for required angle brackets.
Moved config key check.bad_rcpt to reject.bad_rcpt.
- Added tests for bounce_spf() - Refactored most of the existing tests to be more thorough.
Moved config key check.bad_rcpt to reject.bad_rcpt
index.js
Outdated
// legacy settings | ||
const c = this.cfg | ||
if (c.check.reject_all) c.reject.all_bounces = c.check.reject_all | ||
if (c.check.reject) c.reject.reject = c.check.reject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't look right
index.js
Outdated
|
||
next(DENY, 'this bounce message does not have 1 recipient') | ||
return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return next() | |
next() |
Avoid a useless use of return
here.
index.js
Outdated
} | ||
|
||
transaction.results.add(this, { fail: 'empty_return_path', emit: true }) | ||
return next(DENY, 'bounce with non-empty Return-Path (RFC 3834)') | ||
return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return next() | |
next() |
Again, avoid useless use of return
.
index.js
Outdated
|
||
next() | ||
return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return next() | |
next() |
avoid useless use of return. (The calling function does nothing with the functions return. Not using return next...
everywhere makes it subtly more obvious that the return next...
patterns above are strictly for flow control.)
index.js
Outdated
} | ||
|
||
exports.has_null_sender = function (connection, mail_from) { | ||
const transaction = connection?.transaction | ||
if (!transaction) return false | ||
const { transaction } = connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're no longer guarding against the transaction having been destroyed. How can you be certain that when this function is called, the transaction still exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under what circumstances could the transaction be destroyed before one of these hooks gets called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's most common when the remote disconnects in the middle of the conversation. Any hooks that were running before the connection dropped will still be running. The transaction may get cleaned up while a plugin hook is running, especially if the hook does anything asynchronous. Then subsequent attempts to access connection.transaction
will be undefined and trigger an exception, which takes down the node process.
index.js
Outdated
// {fail: 'Message-ID not local', emit: true }); | ||
// if (!plugin.cfg.reject.non_local_msgid) return next(); | ||
// return next(DENY, "bounce with non-local Message-ID (RFC 3834)"); | ||
return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return next() | |
next() |
index.js
Outdated
return next(DENY, 'Invalid bounce (spoofed sender)') | ||
} | ||
|
||
return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return next() | |
next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this PR, especially the improvement of config naming.
Removed spurious line of code
- Removed description for reject_all - Added description for all_bounces
Removed useless use of return
Removed useless use of return
Restored transaction guarding
Added test to check for missing transaction
Removed spurious line of code. |
index.js
Outdated
@@ -103,16 +120,19 @@ exports.single_recipient = function (next, connection) { | |||
|
|||
transaction.results.add(this, { fail: 'single_recipient', emit: true }) | |||
|
|||
if (!this.cfg.reject.single_recipient) return next() | |||
if (this.cfg.reject.single_recipient) { | |||
next(DENY, 'this bounce message has too many recipients') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next(DENY, 'this bounce message has too many recipients') | |
return next(DENY, 'this bounce message has too many recipients') |
You do need the return here, for flow control. Otherwise you'll be calling next()
twice.
index.js
Outdated
const rcpt = element.address() | ||
if (!this.cfg.invalid_addrs[rcpt]) continue | ||
transaction.results.add(this, { fail: 'bad_rcpt', emit: true }) | ||
if (this.cfg.invalid_addrs.includes(rcpt.address())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the result of rcpt.address()
always lower case?
index.js
Outdated
const transaction = connection?.transaction | ||
if (!transaction) return false | ||
const { transaction } = connection | ||
if (!transaction) return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!transaction) return next() | |
if (!transaction) return false |
There's no next()
in context here.
Perhaps a good test to add for each bounce test is passing in a connection without a transaction, and assuring that the function returns (doesn't trigger an exception). |
minor cose cleanup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the last few issues I see.
} | ||
} | ||
|
||
exports.non_local_msgid = function (next, connection) { | ||
if (!this.cfg.check.non_local_msgid) return next() | ||
if (!this.has_null_sender(connection)) return next() | ||
|
||
const transaction = connection?.transaction | ||
if (!transaction) return next() | ||
const { transaction } = connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { transaction } = connection | |
const { transaction } = connection | |
if (!transaction) return |
@@ -293,24 +305,22 @@ function find_received_headers(ips, body, connection, self) { | |||
} | |||
|
|||
exports.bounce_spf_enable = function (next, connection) { | |||
if (!connection.transaction) return next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restore transaction guarding here.
if (!this.cfg.check.bounce_spf) return next() | ||
if (!this.has_null_sender(connection)) return next() | ||
|
||
const txn = connection?.transaction | ||
if (!txn) return next() | ||
const { transaction } = connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { transaction } = connection | |
const { transaction } = connection | |
if (!transaction) return next() |
- added tests for transaction guarding - moved transaction guarding from has_null_sender() to each hook - refactored has_null_sender()
Added tests for guarding transactions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
A final issue is documentation. You've added the use of three new config files to the plugin:
- host_list
- bounce_bad_rcpt
- bounce_allowed_msgids
None of those files are mentioned in the README, nor is the format for what you'd put in them mentioned. There are alternatives to accomplish this:
1. Move the data into bounce.ini and add inline docs there
[check]
single_recipient=true
empty_return_path=true
[reject]
single_recipient=true
empty_return_path=true
; reject all bounce messages (generally a bad idea)
all_bounces=false
; reject bounces to "bad" recipients
bad_rcpt=true
; bad_rcpts[] = [email protected]
; bad_rcpts[] = [email protected]
; bad_rcpts[] = ....
; to validate bounce message IDs, add an entry to the domains array
; for each email domain you accept email for.
[msgid]
; domains[] = example.com
; domains[] = example.net
; domains[] = example.org
2. Add a section to the README for each config file
Explain why you'd use the file and what the contents of the file should be.
Finally, the local msgid checks the domain against the contents of the host_list file. In my case, there's 4 domains listed in hosts_list, but that file hasn't been updated since 2021 and the rcpt_to.in_hosts_list plugin isn't even loaded. I think you want to say something like this:
- The Message-ID test needs to know what domains are 'local' to your mail server. If you use the rcpt_to.host_list plugin and have populated the
config/host_list
file, then you're done. - If you don't use that plugin, make sure the host_list file is empty and populate the [msgid].domains[] array in bounce.ini (or config/bounce_allowed_msgids).
Also, if you keep the file name bounce_allowed_msgids, a name that better indicates what goes in it might be bounce_msgid_allowed_domains?
updated settings to match defaults in source
- updated documentation for all_bounces and bad_rcpt - added documentation for non_local_msgid
minor code clean up
cleaned up test code
Changes proposed in this pull request:
Fixes #
Checklist: