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
Conversation
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
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'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 { |
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 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.
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.
Thanks for the quick response, yes. I'd be happy to refactor this into another method if the maintainer is OK with it.
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.
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?
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) |
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.
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.
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.
+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.
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! |
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.
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") { |
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.
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 { |
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.
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 { |
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 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 { |
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 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.
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) |
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.
+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.
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. 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! |
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: 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. |
Of course, no worries, and there's nothing to apologize for!
It's a minor point overall, but for what it's worth, msmtp works fine with I've never used sendemail but it seems to support PLAIN. More on this below.
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! |
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! |
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.
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.