bbottema / simple-java-mail

Simple API, Complex Emails (Jakarta Mail smtp wrapper)
http://www.simplejavamail.org
Apache License 2.0
1.22k stars 264 forks source link

Problem of losing the existing boundary value when creating an Email object. #460

Closed honorhs closed 1 year ago

honorhs commented 1 year ago

Hello :)

When relaying an email using simpleJavaMail, the boundary value can be changed, for example, when converting an EML file to a MimeMessage and then to an Email using the EmailConverter.emlToEmail(...) method, a new boundary is created during the MimeMessage to Email conversion process.

This issue can cause DKIM authentication failures when relaying emails through an SMTP server created with simpleJavaMail.

It would be helpful to have an option to either maintain the existing boundary value or create a new one when creating an Email object.

Thank you for your consideration.

bbottema commented 1 year ago

Can you elaborate, please? What is a boundary value?

honorhs commented 1 year ago

@bbottema

In the body of an email, the boundary value used in a multipart Content-Type indicates the boundary for separating different parts of the message. Assuming the following flow for sending an email: A SMTP server -> B SMTP server (using SimpleJavaMail) -> Gmail.

When the email passes from A SMTP server to B SMTP server, the boundary value for the multipart type is redefined when the MimeMessage or EML is converted to a SimpleJavaMail Email. If A SMTP server signs the header with a DKIM-Signature for DKIM authentication, and B SMTP server (using SimpleJavaMail) redefines the boundary value, resulting in a different hash value for the message body, DKIM authentication will fail.

there won't be any issues if DKIM signing is done in B SMTP(using SimpleJavaMail). However, if DKIM signing is done in a previous SMTP server and the email is forwarded to a relay server implemented with SimpleJavaMail, DKIM authentication will fail.

Is there any feature available to prevent such issues?

bbottema commented 1 year ago

I don't think this is within the influence sphere of Simple Java Mail. This sounds like an implementation detail of the underlying Jakarta Mail library. If what you are saying is correct, then I'm afraid the only way to deal with this is by signing it again.

bbottema commented 1 year ago

After giving it some thought, my head cleared up and I would like to revise my reply.

Of course this is not going to work if you parse and generate a new email. The hash based on the first version will never (and cannot) be the same as the first. Not only is the structure probably different (as Simple Java Mail has its own way of deciding how to structure mime components), email ID's and send dates among other might be generated again, resulting in a new email that only resembles the original.

What you are trying to do is inherently impossible, because that is exactly what a DKIM signature is about: proving that the email has not been tampered with. The only thing you can do is parse it, read its content, and pass on the original email to the next server.

honorhs commented 1 year ago

Thank you for providing your opinion. :)

My thoughts are a little different.. That could mean that the email system implemented with SimpleJavaMail is excellent but cannot be used for relay SMTP purposes.

Let me give you an example.

When the emlToMimeMessage method of the EmailConverter class provided by SimpleJavaMail is called, the resulting mimeMessage does not change its boundary.

public static MimeMessage emlToMimeMessage(@NotNull final InputStream inputStream) {
    return emlToMimeMessage(inputStream, createDummySession());
}

This means that when creating a MimeMessage from an InputStream, Jakarta Mail does not modify the existing boundary. (Of course, the default behavior would be to create a new boundary value, but when pouring the existing data as is through the InputStream, it does not modify the data of the original email.)

To send the created MimeMessage using SimpleJavaMail's Mailer, it needs to be converted to an Email. However, when executing the statement EmailBuilder.copying(mimeMessage).buildEmail(), the boundary value changes.

This is because the Email class attempts to create a new DataSource by only receiving the data when the type is Multipart.

Personally, I thought that if we follow the Jakarta Mail specification as is, the boundary value should be maintained. At least, when creating an Email object through an InputStream, the boundary value should be maintained.

Just for reference, there is no issue with DKIM authentication when implementing a relay SMTP service using pure Jakarta Mail. This means that Jakarta Mail can relay the original email without modifying the original data.

bbottema commented 1 year ago

Thank you for coming back to this. You raise an interesting point of view.

First, I want to clarify that Simple Java Mail has a different philosophy compared to Jakarta Mail. Its mission is to provide an accessible API that can be used to create complex emails, properly composed of nested Mime components, to guarantee a proper display in email clients. As there are often multiple ways to model a mime structure to serve a specific use case, Simple Java Mail is explicitly not designed to maintain any original structure per se (it will take what's there, but always generate the most correct and limited-to-essentials per use case structure possible).

However, we should recognize that Simple Java Mail has evolved to a point where the reading, parsing, generating, and validation facilities can be regarded separately from the sending and logging facilities. As such, it makes sense for the API to offer a new method for sending precompiled MimeMessage objects, say: Mailer.relay(MimeMessage). This way, you can benefit from Simple Java Mail's performant sending functions while not having to convert between MimeMessage and Email, and therefore retaining the original hash (boundaries).

Your thoughts?

honorhs commented 1 year ago

Thank you for sharing your thoughts.

First of all, I understand and agree with the fact that Simple Java Mail and Jakarta Mail have different philosophies.

When I first encountered Simple Java Mail, I was attracted to its simple interface and the fact that it provides many features. I have used various SDKs for sending emails, but it's hard to find an SDK that is as convenient as Simple Java Mail, especially with features such as the batch module (https://www.simplejavamail.org/modules.html#batch-module).

However, as the number of users increases and more requirements arise, it may become difficult to maintain a simple structure. Perhaps it may not be possible to maintain the existing philosophy. Nevertheless, I don't doubt that these issues are simply part of the process of Simple Java Mail evolving.

I believe that Simple Java Mail could become an SDK that provides various mail-related APIs, similar to Apache James. (I could be mistaken.)

Returning to the point, it would be very attractive if the Mailer could relay mimeMessage. I confidently think that this would be a feature that could be attractive in various places.

Simply put, with just the relay feature, it would be possible to create a postfix-like sending agent using a combination of a lightweight JVM and Simple Java Mail, without putting in too much effort. This could replace older agents like postfix and others.

honorhs commented 1 year ago

For your information, I am not good at English. 😓 I am so grateful for you taking the time to read my long message.

honorhs commented 1 year ago

@bbottema Is the relay feature you mentioned going to be developed in the future?

bbottema commented 1 year ago

Absolutely! I'll make a new ticket for it though.

honorhs commented 1 year ago

Thank you for your attention to this matter. :)

honorhs commented 1 year ago

@bbottema Are there any updates?