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:
Jim Nelson 2013-06-05 12:35:43 -07:00
parent 696a9f7d95
commit 547158b9dd
2 changed files with 79 additions and 23 deletions

View file

@ -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 {

View file

@ -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;
}