go-smtp-mock
go-smtp-mock copied to clipboard
[FEATURE] Support multiple RCPT TO addresses on the same session
- [x] I have updated
go-smtp-mock
to the latest version - [x] I have read the Contribution Guidelines
- [x] I have read the documentation
- [x] I have searched for existing GitHub issues
Please could we add the ability to specify multiple recipients by being able to send the RCPT TO: multiple times with different addresses on the same session.
At the moment the framework simply replaces the last rcpt address with the newly requested on
It should be ok because this is how the rfc defines it : https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.1.3
Perhaps we can change the rcpttoRequest type in the Message struct to a slice and append the to address of every RCPT TO:
I am happy to have a go, I am just checking in with the idea.
@dandare100 Hahaha, I knew this question would be asked someday) Agree! Let's back for this point at the beginning of October 🍻
@dandare100 Hahaha, I knew this question would be asked someday) Agree! Let's back for this point at the beginning of October 🍻
Cool, cheers
:+1:
This is essential for testing emailer implementations. It should indeed be a string slice in sequential order of RCPT
commands.
e.g. if the emailer implementation is sending the email to two recipents "a" and "b" on the "example" domain, then the RcpttoRequest()
method on a Message
should return:
[]string{"RCPT TO:<[email protected]>", "RCPT TO:<[email protected]>"}
@bestwebua I'm happy to give this a go, if you can point me to the bits of the code that would need changing. The current implementation seems to do some "writing" that involves some significant indirection that I would rather not spend the time trying to unravel if you could just explain it to me?
@benjamin-rood Sounds good. Okay, but let's complete race condition fix firsly ;)
Mates, @dandare100, @benjamin-rood! Could somebody provide an example of multiple RCPT TO
command example? I expect that it should be somesing like:
RCPT TO:<[email protected],[email protected]>
RCPT TO:[email protected],[email protected]
And it will produce 2 different messages with different RCPT TO contexts.
It seems like the client sends the first one and then each subsequent one after receiving a 250 Received from the server.
For my quick use on my project I just did this
// Writes handled RCPTTO result to session, message. Always returns true
func (handler *handlerRcptto) writeResult(isSuccessful bool, request, response string) bool {
session, message := handler.session, handler.message
if !isSuccessful {
session.addError(errors.New(response))
}
message.rcpttoRequests = append(message.rcpttoRequests, request)
message.rcpttoResponse, message.rcptto = response, isSuccessful
session.writeResponse(response, handler.configuration.responseDelayRcptto)
return true
}
and
// Structure for storing the result of SMTP client-server interaction. Context-included
// commands should be represented as request/response structure fields
type Message struct {
heloRequest, heloResponse string
mailfromRequest, mailfromResponse string
rcpttoResponse string
rcpttoRequests []string
dataRequest, dataResponse string
msgRequest, msgResponse string
rsetRequest, rsetResponse string
helo, mailfrom, rcptto, data, msg, rset, quitSent bool
}
I had no need for separate messages in my case. I understand your requirement to have multiple messages :-)
When adding the rcpt's, something to be careful of is the clearMessage call below in handler_rcptto.go.
I commented it out because my needs were still met.
I suspect that clear to have something to do with the "-multipleMessageReceiving" option but I didn't look into it further.
// Main RCPTTO handler runner
func (handler *handlerRcptto) run(request string) {
handler.clearError()
//I scheme it's because of the mutlimessage disting in the config
//handler.clearMessage()
if handler.isInvalidRequest(request) {
return
}
handler.writeResult(true, request, handler.configuration.msgRcpttoReceived)
}
@dandare100 Thanks for your knowledge sharing, Mark! I'll play with some stdlib SMTP clients from ruby, python and go to make sure that it is exactly expected multiple rcpt to
behaviour. Have you screened logs from mikrotik built-in smtp client, am I right?
multipleMessageReceiving
it's about ability to send multiple messages during one session. And entry point for this case is multipleMessageReceiving
enabled flag and RSET
command, please look on this test example:
https://github.com/mocktools/go-smtp-mock/blob/03e6fbe72340d8081c7ab1ed091c7de18c93367c/server_test.go#L189-L267
https://github.com/mocktools/go-smtp-mock/blob/03e6fbe72340d8081c7ab1ed091c7de18c93367c/server_test.go#L231
What you think about next steps of implementation?:
- Add flag
multipleRcptto
- Change
Message
structure to be able to save slice of strings:
type Message struct {
heloRequest, heloResponse string
mailfromRequest, mailfromResponse string
rcpttoRequestResponse [][]string // new fileld with [["request", "response"]]
dataRequest, dataResponse string
msgRequest, msgResponse string
rsetRequest, rsetResponse string
helo, mailfrom, rcptto, data, msg, rset, quitSent bool
}
- Leave current behaviour for case when
multipleRcptto
isfalse
- When
multipleRcptto
istrue
we need to record allRCPT TO
request/response pairs until receive succesful response and next smtp command has been passed. Also message clearing flow should be used for case whenRCPT TO
will passed afterDATA
and upper or when has been usedRSET
P.S.: View for successful send messages we can add later. I believe it should be a slice of "successful mesages" where all multiple messages will be represented as independent messages. For instance, we have 1 single message and 1 multiple message with 2 receivers. Total count of this successful messages should be 3.
lol, yes you are correct re Mikrotik.
The below is for a Java client.
I like your approach, my only question would be why would you add a switch (multipleRcptto )? Why not just let it function in multiple mode as a normal/standard way ?
The client can send 1 message when the server is in multiple mode : no problem
@dandare100 Thanks for java client examples ❤️ I think it can be configured behaviour against hardcoded. It will bring more flexibility and more context even from configuration, for example:
smtpmock.New(smtpmock.ConfigurationAttr{
MultipleRcptto: true,
MultipleMessageReceiving: true,
NotRegisteredEmails: []string{"[email protected]"},
})
Also we should folowing current 'configuring flow' to save our main feature - 'Configurable multithreaded RFC compatible SMTP server'. Agree? 😃
If it okay I'll prepare to 2.x.x migration and will start developing this feature 👨💻
Good points, I agree
@dandare100, @benjamin-rood Already in latest (2.0.0
) release 🚀