emersion / go-imap

📥 An IMAP library for clients and servers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feature: don't request CAPABILITY after auth/login if they are returned in auth response

Rikkuru opened this issue · comments

imapclients invalidates capabilities on login/auth/starttls as it should but login and auth often send capabilities in response, those can be used but they are cleaned too.

From rfc - https://datatracker.ietf.org/doc/rfc9051/

A server MAY include a CAPABILITY response code in the tagged OK
response to a successful LOGIN command in order to send capabilities
automatically. It is unnecessary for a client to send a separate
CAPABILITY command if it recognizes these automatic capabilities.

Here is debug log that demonstrates the problem (with creds removed) - T3 request can be avoided
T1 CAPABILITY\r\n* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID AUTH=PLAIN AUTH=XOAUTH2 IDLE MOVE\r\nT1 OK CAPABILITY Completed.\r\nT2 LOGIN "diana@example.com" "password"\r\n* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID IDLE MOVE\r\nT2 OK LOGIN Completed.\r\nT3 CAPABILITY\r\n* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID IDLE MOVE\r\nT3 OK CAPABILITY Completed.

This is v2 right?

v2 already tries to avoid requesting CAPABILITY if unnecessary, the issue is when something requires capabilities before the server sends them on its own. For instance, if a fresh client is created and then Authenticate is used. Authenticate checks whether the server supports SASL-IR, however a fresh client hasn't waited for the greeting, and we have no guarantee that the server will send the capabilities in its greeting. If we don't request capabilities at this point, then we'll potentially incur a cost of two roundtrips before sending the AUTHENTICATE command.

Which specific commands are you using that trigger the CAPABILITIES request? AFAIU LOGIN shouldn't trigger it.

This is v2.

I think capabilities in response are parsed and then invalideted in the next line from server (when auth/login command ends )
At least i don't see what would prevent it from happening

T2 LOGIN "diana@example.com" "password"
* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID IDLE MOVE # here handleCapability from readResponseData
T2 OK LOGIN Completed. // here invalidate the capabilities from readResponseTagged
T3 CAPABILITY
* CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID IDLE MOVE

Here is what i was doing on login , later i call Caps to deside on options for List call

	if client.Caps().Has(goimap.CapStartTLS) {
		// good servers dont allow login and other plain auth methods without tls
		err = client.StartTLS(tlsConfig())
		if err != nil {
			return fmt.Errorf("StartTLS: %w", err)
		}
	}

	err = client.WaitGreeting()
	if err != nil {
		return fmt.Errorf("WaitGreeting: %w", err)
	}

	if client.Caps().Has(goimap.CapLoginDisabled) {
		return ErrLoginDisabled
        return client.Login(b.connSettings.Login, b.connSettings.Secret).Wait()

You need to WaitGreeting before the STARTTLS stuff. That should help, I think?

oh sorry this was a tls conn so starttls branch should not be here

updated flow is just create client , waitgreeting, check logindisabled (this code is common for tls and plain conn ) , login

LOGINDISABLED capability check is what initiated T1 capability command
then we login
T3 is initiated by next capability check after login wait

		client, err = dialTLS(
			host, port,
			timeout,
			tlsConfig(),
		)
		if err != nil {
			return fmt.Errorf("dialTLS: %w", err)
		}
		err = client.WaitGreeting()
		if err != nil {
			return fmt.Errorf("WaitGreeting: %w", err)
		}
	
		if client.Caps().Has(goimap.CapLoginDisabled) { // T1
			return ErrLoginDisabled
		return client.Login(b.connSettings.Login, b.connSettings.Secret).Wait() // T2

next list calls Caps

	if client.Caps().Has(goimap.CapSpecialUse) { // T3
		listOptions.ReturnSpecialUse = true
	}

I can write a complite example that reproduces the problem later ( gmail returns capabilities in login for example )

Ah, OK, so the problem is not the CapLoginDisabled check. GMail doesn't return the capabilities in its greeting.

The problem is that the capabilities are sent in a response separate from the OK from LOGIN.

Section 7.2.2 says:

A server MAY send capabilities automatically, by using the CAPABILITY response code in the initial PREAUTH or OK responses and by sending an updated CAPABILITY response code in the tagged OK response as part of a successful authentication.

However GMail doesn't comply with this.

so they mean server should send CAPABILITY response code in LOGIN response ?
like so ?

C: T1 LOGIN foo bar
S: T1 OK CAPABILITY IMAP4rev2 IDLE

seems wrong

Like this:

T2 OK [CAPABILITY IMAP4rev1 CHILDREN UNSELECT LITERAL+ NAMESPACE XLIST UIDPLUS ENABLE ID IDLE MOVE] LOGIN Completed.

I found an example of this

1 LOGIN user pass
1 OK [CAPABILITY IMAP4REV1 UIDPLUS] Logged in.

Ok this seems like what they mean by this
If you know where in rfc they explain those square brackets pls let me know too )

Section 7.1 describes these.