Skip to content
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

Support the LOGIN method to expand client compatibility #46

Closed
wants to merge 3 commits into from

Conversation

erjoalgo
Copy link
Contributor

@erjoalgo erjoalgo commented Dec 9, 2023

Some clients do support TLS but use the LOGIN method, where the client
waits for a password prompt before sending the password, as opposed to the
currently-supported PLAIN method, where the client sends both username and
password as part of a single request. This minimal change attempts to support
the LOGIN method by parsing credentials, and then converting them into a
PLAIN-like auth string.

This prevents chasquid from attempting to look for certs under
a non-directory, e.g. ./etc/chasquid/domains/.gitignore/certs
Some clients do support TLS but use the LOGIN method, where the client
waits for a password prompt before sending the password, as opposed to the
currently-supported PLAIN method, where the client sends both username and
password as part of a single request. This minimal change attempts to support
the LOGIN method by parsing credentials, and then converting them into a
PLAIN-like auth string.

Example such client:

https://salsa.debian.org/debian/ssmtp/-/blob/master/ssmtp.c#L1513
Copy link
Contributor

@ThinkChaos ThinkChaos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitely not an authority on any of this, but took a quick look cause I'm a user of chasquid and know Go well enough :)
So as a user, thanks for the PR, always nice to have better client compatibility!

@@ -1047,6 +1048,39 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
response := ""
if len(sp) == 2 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this condition needs to be adjusted cause current code would enter it with LOGIN something.

But I'd say it's probably a better idea to add a Conn.LOGIN method instead to make this kind of bug impossible. Common code could be factored out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick response, yes. I'd be happy to refactor this into another method if the maintainer is OK with it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to both.

I agree it would be better to refactor this.

I would keep the AUTH function and then split lines 1046-1070 in a separate function for PLAIN, and call either that or a new LOGIN function based on the method the client is using.

Does that seem reasonable to you?

Comment on lines +1077 to +1083
plain := []byte{}
plain = append(plain, user...)
plain = append(plain, '\000')
plain = append(plain, user...)
plain = append(plain, '\000')
plain = append(plain, pass...)
response = base64.StdEncoding.EncodeToString(plain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other comment I left, I think this would greatly benefit from splitting the function: it'd be easier and less error prone have the last part of this function be another function that takes user, domain, passwd string arguments.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 I think this part can be ommitted if there is a refactor as mentioned in the other comment.

The helper functions can return user, domain and pass, and then AUTH can use that just like it does today.

@albertito
Copy link
Owner

Hi! Thanks a lot for sending this, and to @ThinkChaos for the review!

I just wanted to let you know that I am travelling with limited connectivity, sorry for the delay! I hope to get to this tomorrow!

Copy link
Owner

@albertito albertito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for doing this!

I left comments on this specific code, but will discuss something more general in the "conversation" tab (sorry for the split but github code reviews are quite messy :S).

@@ -1035,7 +1036,7 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
// response back from the client in the next message.

sp := strings.SplitN(params, " ", 2)
if len(sp) < 1 || sp[0] != "PLAIN" {
if len(sp) < 1 || (sp[0] != "PLAIN" && sp[0] != "LOGIN") {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW line 361 also need to be edited to add LOGIN: AUTH PLAIN LOGIN.

@@ -1047,6 +1048,39 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
response := ""
if len(sp) == 2 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to both.

I agree it would be better to refactor this.

I would keep the AUTH function and then split lines 1046-1070 in a separate function for PLAIN, and call either that or a new LOGIN function based on the method the client is using.

Does that seem reasonable to you?

// parameters and convert them into the PLAIN authentication
// format, i.e. the base64-encoded string:
// <authorization id> NUL <authentication id> NUL <password>
if err := c.writeResponse(334, ""); err != nil {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the server needs to write VXNlciBOYW1lAA== (the base64 encoded of User Name\0) here.

https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt section 2.2 and 2.3.

return 554, fmt.Sprintf("5.4.0 Error reading AUTH LOGIN user response: %v", err)
} else if user, err = base64.StdEncoding.DecodeString(userb64); err != nil {
return 554, fmt.Sprintf("5.4.0 Error parsing AUTH LOGIN user 334: %v", err)
} else if err := c.writeResponse(334, ""); err != nil {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the server needs to write UGFzc3dvcmQA (the base64 encoded of Password\0) here.

https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt section 2.2 and 2.3.

Comment on lines +1077 to +1083
plain := []byte{}
plain = append(plain, user...)
plain = append(plain, '\000')
plain = append(plain, user...)
plain = append(plain, '\000')
plain = append(plain, pass...)
response = base64.StdEncoding.EncodeToString(plain)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 I think this part can be ommitted if there is a refactor as mentioned in the other comment.

The helper functions can return user, domain and pass, and then AUTH can use that just like it does today.

@albertito
Copy link
Owner

albertito commented Dec 12, 2023

I just did a first pass review, overall it looks good, and most of the suggestions are just around reordering code, but I hope it's nothing too problematic.

This still would need a lot of testing but I'm happy to do that part.

All that said, I have to wonder about the benefit of implementing this method vs. the complexity added.

The LOGIN method has been obsolete (IANA SASL mechanisms) for decades.
As far as I can tell it was never really standarized, draft-murchison-sasl-login-00.txt is the closest it got.

And it is basically a more complex variant of the PLAIN method (which is a solid standard and widely used).

So I wonder how many additional "reasonably used clients" this would allow chasquid to interoperate with.

Do you know of any besides ssmtp? If ssmtp is the only one we can find (that is reasonably used), then it might be more practical to send a patch adding PLAIN support to it instead?

What do you think?

Thanks again!

PS: I think the other two patches are useful in any case and I'm inclined to merge them regardless, but let's focus on the LOGIN method first just for clarity of the conversation!

@erjoalgo
Copy link
Contributor Author

Sorry, I've been a little busy, I will take a look next week.

Basically, to test my setup I looked for a few SMTP clients packaged on debian and I initially couldn't get any of them to work out of the box with my chasquid server, so I suspected there was something missing either with my setup or with chasquid, or with my DNS, etc. I tried msmtp, sendemail, and ssmtp. I picked one of these clients. the first one happened to be ssmtp, looked at the source and noticed the problem was the PLAIN vs LOGIN auth, which I fixed and manually tested. Later, I tried fixing msmtp and noticed that it had failed not because of a fundamental incompatibility but because I had failed to specify a number of specific flag values, including: --auth=plain, --tls=on, and --tls-starttls=off. I was never able to get sendemail to work even after installing the requied perl TLS libraries.

I do not know how many clients prefer LOGIN over PLAIN, but in an ideal world most clients should work with most servers out of the box without the need for users to spend too much time tweaking or debugging, especially since most users just care about having a working email server and client, but are probably not willing to dig down and study the sources and compare the server and client to determine why it's not working. If chasquid can be a server that "just works" under most circumstances and make things easy for users and clients then I think it can become even more useful and powerful than it is already.

@albertito
Copy link
Owner

albertito commented Dec 20, 2023

Sorry, I've been a little busy, I will take a look next week.

Of course, no worries, and there's nothing to apologize for!

Basically, to test my setup I looked for a few SMTP clients packaged on debian and I initially couldn't get any of them to work out of the box with my chasquid server, so I suspected there was something missing either with my setup or with chasquid, or with my DNS, etc. I tried msmtp, sendemail, and ssmtp. I picked one of these clients. the first one happened to be ssmtp, looked at the source and noticed the problem was the PLAIN vs LOGIN auth, which I fixed and manually tested.
Later, I tried fixing msmtp and noticed that it had failed not because of a fundamental incompatibility but because I had failed to specify a number of specific flag values, including: --auth=plain, --tls=on, and --tls-starttls=off. I was never able to get sendemail to work even after installing the requied perl TLS libraries.

It's a minor point overall, but for what it's worth, msmtp works fine with auth=on as it can negotiate the method, which is what most clients would do. This is also covered in integration tests (many of them use msmtp).

I've never used sendemail but it seems to support PLAIN. More on this below.

I do not know how many clients prefer LOGIN over PLAIN, but in an ideal world most clients should work with most servers out of the box without the need for users to spend too much time tweaking or debugging, especially since most users just care about having a working email server and client, but are probably not willing to dig down and study the sources and compare the server and client to determine why it's not working. If chasquid can be a server that "just works" under most circumstances and make things easy for users and clients then I think it can become even more useful and powerful than it is already.

I understand this is a frustrating experience, and I really appreciate you sending a patch to make things better.

In practice, I do my best to balance ease of use and flexibility with security, maintainability and reliability. Sometimes the tradeoffs are obvious, but sometimes they are not.

To me, in this case, unfortunately I am not sure the benefits outweigh the costs in terms of complexity.

Another example of a similar case is that chasquid only supports authentication over TLS, even if the client is in localhost, where it could be argued it provides a negligible value. This means client setup on localhost is more complicated, but has the upside of making it less likely to have security bugs around authentication.

However, it is clear from your experience that client set up can be non-intuitive and require more research/trialling than it should.

So how about we add a "Clients" document, where we list different clients and have brief and practical configuration examples, to reduce that friction? And it could include a section on unsupported clients as well. That can help offset some of the difficulty, and cover other aspects you faced, in addition to the login method part.

I'd be happy to write a draft and iterate on that with you to help future users.

What do you think?

Thanks again!

@erjoalgo
Copy link
Contributor Author

Thanks for the thorough reply. I disagree that this change is not worth implementing. I believe LOGIN is a valid SMTP authentication method which valid SMTP clients may choose to employ, and I think it's possible to support it for all clients without compromising security. I do appreciate and respect your work developing and maintaining chasquid, and I respect your opinion so I am closing this PR. I do think providing a client's section would be useful, though I think it would be easier for users if compatibility could be improved on the server side.

@albertito
Copy link
Owner

Thanks for the thorough reply. I disagree that this change is not worth implementing. I believe LOGIN is a valid SMTP authentication method which valid SMTP clients may choose to employ, and I think it's possible to support it for all clients without compromising security. I do appreciate and respect your work developing and maintaining chasquid, and I respect your opinion so I am closing this PR. I do think providing a client's section would be useful, though I think it would be easier for users if compatibility could be improved on the server side.

Thank you so much for your understanding, even though we disagree. I know it is not great to have a change rejected, and I'm sorry about that, but still really appreciate it and the very useful discussion around it.

I would like to merge the other two patches, if that's okay with you?

Also, opened #48 to track the documentation improvements around setting up clients.

Thanks again!

albertito added a commit that referenced this pull request Feb 6, 2024
We've had a couple of reported issues about the difficulty of setting up
new clients, or confusion due to using broken clients:

- #46
- #52

This patch adds the first version of a "Clients" document that includes
requirements for all clients, configuration examples, and a list of
known-problematic client software.

The goal is to help reduce friction and confusion when setting up
clients.

The document needs more polishing and examples, which hopefully will be
added later.

Fixes #48.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants