Fallback to other SMTP authentication schemes: Closes #6091
This introduces an STMP authentication fallback scheme. It first attempts advertised schemes, then falls back to unadvertised (but commonly-implemented) schemes. In particular, this is to deal with certain servers that advertise support for an authentication scheme but not actually implement it.
This commit is contained in:
parent
696a9f7d95
commit
547158b9dd
2 changed files with 79 additions and 23 deletions
|
|
@ -46,31 +46,74 @@ public class Geary.Smtp.ClientSession {
|
|||
notify_connected(greeting);
|
||||
|
||||
// authenticate if credentials supplied (they should be if ESMTP is supported)
|
||||
if (creds != null) {
|
||||
// detect which authentication is available, using PLAIN if none found as a hail mary
|
||||
Authenticator? authenticator = null;
|
||||
if (cx.capabilities != null) {
|
||||
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN))
|
||||
authenticator = new PlainAuthenticator(creds);
|
||||
else if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN))
|
||||
authenticator = new LoginAuthenticator(creds);
|
||||
}
|
||||
|
||||
if (authenticator == null)
|
||||
authenticator = new PlainAuthenticator(creds);
|
||||
|
||||
debug("[%s] Using %s authenticator", to_string(), authenticator.to_string());
|
||||
|
||||
Response response = yield cx.authenticate_async(authenticator, cancellable);
|
||||
if (!response.code.is_success_completed())
|
||||
throw new SmtpError.AUTHENTICATION_FAILED("Unable to authenticate with %s", to_string());
|
||||
|
||||
notify_authenticated(authenticator);
|
||||
}
|
||||
if (creds != null)
|
||||
notify_authenticated(yield attempt_authentication_async(creds, cancellable));
|
||||
|
||||
return greeting;
|
||||
}
|
||||
|
||||
// Returns authenticator used for successful authentication, otherwise throws exception
|
||||
private async Authenticator attempt_authentication_async(Credentials creds, Cancellable? cancellable)
|
||||
throws Error {
|
||||
// build an authentication style ordering to attempt, going from reported capabilities to standard
|
||||
// fallbacks, while avoiding repetition ... this is necessary due to server bugs that report
|
||||
// an authentication type is available but actually isn't, see
|
||||
// http://redmine.yorba.org/issues/6091
|
||||
// and
|
||||
// http://comments.gmane.org/gmane.mail.pine.general/4004
|
||||
Gee.ArrayList<string> auth_order = new Gee.ArrayList<string>(String.stri_equal);
|
||||
|
||||
// start with advertised authentication styles, in order of our preference (PLAIN
|
||||
// only requires one round-trip)
|
||||
if (cx.capabilities != null) {
|
||||
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_PLAIN))
|
||||
auth_order.add(Capabilities.AUTH_PLAIN);
|
||||
|
||||
if (cx.capabilities.has_setting(Capabilities.AUTH, Capabilities.AUTH_LOGIN))
|
||||
auth_order.add(Capabilities.AUTH_LOGIN);
|
||||
}
|
||||
|
||||
// fallback on commonly-implemented styles, again in our order of preference
|
||||
if (!auth_order.contains(Capabilities.AUTH_PLAIN))
|
||||
auth_order.add(Capabilities.AUTH_PLAIN);
|
||||
|
||||
if (!auth_order.contains(Capabilities.AUTH_LOGIN))
|
||||
auth_order.add(Capabilities.AUTH_LOGIN);
|
||||
|
||||
// in current situation, should always have one authentication type to attempt
|
||||
assert(auth_order.size > 0);
|
||||
|
||||
// go through the list, in order, until one style is accepted
|
||||
do {
|
||||
Authenticator? authenticator;
|
||||
switch (auth_order.remove_at(0)) {
|
||||
case Capabilities.AUTH_PLAIN:
|
||||
authenticator = new PlainAuthenticator(creds);
|
||||
break;
|
||||
|
||||
case Capabilities.AUTH_LOGIN:
|
||||
authenticator = new LoginAuthenticator(creds);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
debug("[%s] Attempting %s authenticator", to_string(), authenticator.to_string());
|
||||
|
||||
Response response = yield cx.authenticate_async(authenticator, cancellable);
|
||||
if (response.code.is_success_completed())
|
||||
return authenticator;
|
||||
|
||||
// syntax errors indicate the command was unknown or unimplemented, i.e. unavailable
|
||||
// authentication type, so try again, otherwise treat as authentication failure
|
||||
if (!response.code.is_syntax_error())
|
||||
break;
|
||||
} while (auth_order.size > 0);
|
||||
|
||||
throw new SmtpError.AUTHENTICATION_FAILED("Unable to authenticate with %s", to_string());
|
||||
}
|
||||
|
||||
public async Response? logout_async(Cancellable? cancellable = null) throws Error {
|
||||
Response? response = null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class Geary.Smtp.ResponseCode {
|
|||
public ResponseCode(string str) throws SmtpError {
|
||||
// these two checks are sufficient to make sure the Status is valid, but not the Condition
|
||||
if (str.length != STRLEN)
|
||||
throw new SmtpError.PARSE_ERROR("Reply code too long: %s", str);
|
||||
throw new SmtpError.PARSE_ERROR("Reply code wrong length: %s (%d)", str, str.length);
|
||||
|
||||
int as_int = int.parse(str);
|
||||
if (as_int < MIN || as_int > MAX)
|
||||
|
|
@ -109,7 +109,20 @@ public class Geary.Smtp.ResponseCode {
|
|||
public bool is_denied() {
|
||||
return str == DENIED_CODE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true for [@link Status.PERMANENT_FAILURE} {@link Condition.SYNTAX} errors.
|
||||
*
|
||||
* Generally this means the command (or sequence of commands) was unknown or unimplemented,
|
||||
* i.e. "500 Syntax error", "502 Command not implemented", etc.
|
||||
*
|
||||
* See [[http://tools.ietf.org/html/rfc5321#section-4.2.2]]
|
||||
*/
|
||||
public bool is_syntax_error() {
|
||||
return get_status() == ResponseCode.Status.PERMANENT_FAILURE
|
||||
&& get_condition() == ResponseCode.Condition.SYNTAX;
|
||||
}
|
||||
|
||||
public string serialize() {
|
||||
return str;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue