diff --git a/FAQ.md b/FAQ.md
index 531434e1ca..7f20657ee9 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -1522,11 +1522,11 @@ The following authentication methods are supported and used in this order:
* LOGIN
* PLAIN
+* CRAM-MD5
* NTLM (untested)
-SASL authentication methods, like CRAM-MD5, are not supported
+SASL authentication methods, besides CRAM-MD5, are not supported
because [JavaMail for Android](https://javaee.github.io/javamail/Android) does not support SASL authentication.
-If using secure connections, a must today, there is little value in using CRAM-MD5 anyway.
If your provider requires an unsupported authentication method, you'll likely get the error message *authentication failed*.
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 40c9810559..2044b1fd8c 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -100,3 +100,6 @@
-dontnote com.bugsnag.android.**
-dontnote biweekly.io.**
+#SASL
+-keep class com.sun.mail.imap.protocol.IMAPSaslAuthenticator
+-keep class com.sun.mail.smtp.SMTPSaslAuthenticator
diff --git a/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java b/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java
new file mode 100644
index 0000000000..a1a79fff0f
--- /dev/null
+++ b/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java
@@ -0,0 +1,103 @@
+package com.sun.mail.imap.protocol;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2020 by Marcel Bokhorst (M66B)
+*/
+
+import android.util.Base64;
+
+import com.sun.mail.iap.Argument;
+import com.sun.mail.iap.ProtocolException;
+import com.sun.mail.iap.Response;
+import com.sun.mail.util.MailLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import eu.faircode.email.Helper;
+
+// https://github.com/javaee/javamail/blob/master/mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java
+public class IMAPSaslAuthenticator implements SaslAuthenticator {
+ private IMAPProtocol pr;
+ private String name;
+ private Properties props;
+ private MailLogger logger;
+ private String host;
+
+ public IMAPSaslAuthenticator(
+ IMAPProtocol pr, String name, Properties props,
+ MailLogger logger, String host) {
+ this.pr = pr;
+ this.name = name;
+ this.props = props;
+ this.logger = logger;
+ this.host = host;
+ }
+
+ @Override
+ public boolean authenticate(
+ String[] mechs, final String realm,
+ final String authzid, final String u,
+ final String p) throws ProtocolException {
+
+ if (!pr.hasCapability("AUTH=CRAM-MD5"))
+ throw new UnsupportedOperationException();
+
+ List v = new ArrayList<>();
+ String tag = null;
+ Response r = null;
+ boolean done = false;
+
+ try {
+ Argument args = new Argument();
+ args.writeAtom("CRAM-MD5");
+ tag = pr.writeCommand("AUTHENTICATE", args);
+ } catch (Exception ex) {
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+
+ while (!done) {
+ try {
+ r = pr.readResponse();
+ if (r.isContinuation()) {
+ byte[] nonce = Base64.decode(r.getRest(), Base64.NO_WRAP);
+ String hmac = Helper.HMAC("MD5", 64, p.getBytes(), nonce);
+ String hash = Base64.encodeToString((u + " " + hmac).getBytes(), Base64.NO_WRAP) + "\r\n";
+ pr.getIMAPOutputStream().write(hash.getBytes());
+ pr.getIMAPOutputStream().flush();
+ } else if (r.isTagged() && r.getTag().equals(tag))
+ done = true;
+ else if (r.isBYE())
+ done = true;
+ } catch (Exception ex) {
+ r = Response.byeResponse(ex);
+ done = true;
+ }
+ v.add(r);
+ }
+
+ Response[] responses = v.toArray(new Response[0]);
+ pr.handleCapabilityResponse(responses);
+ pr.notifyResponseHandlers(responses);
+ pr.handleLoginResult(r);
+ pr.setCapabilities(r);
+ return true;
+ }
+}
diff --git a/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java b/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java
new file mode 100644
index 0000000000..a76ce2a2db
--- /dev/null
+++ b/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java
@@ -0,0 +1,101 @@
+package com.sun.mail.smtp;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2020 by Marcel Bokhorst (M66B)
+*/
+
+import android.util.Base64;
+
+import com.sun.mail.util.MailLogger;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+
+import eu.faircode.email.Helper;
+import eu.faircode.email.Log;
+
+// https://github.com/javaee/javamail/blob/master/mail/src/main/java/com/sun/mail/smtp/SMTPSaslAuthenticator.java
+public class SMTPSaslAuthenticator implements SaslAuthenticator {
+ private SMTPTransport pr;
+ private String name;
+ private Properties props;
+ private MailLogger logger;
+ private String host;
+
+ public SMTPSaslAuthenticator(
+ SMTPTransport pr, String name,
+ Properties props, MailLogger logger, String host) {
+ this.pr = pr;
+ this.name = name;
+ this.props = props;
+ this.logger = logger;
+ this.host = host;
+ }
+
+ @Override
+ public boolean authenticate(
+ String[] mechs, final String realm,
+ final String authzid, final String u,
+ final String p) throws MessagingException {
+
+ if (!pr.supportsAuthentication("CRAM-MD5"))
+ throw new UnsupportedOperationException();
+
+ // https://tools.ietf.org/html/rfc4954
+ int resp = simpleCommand(pr, "AUTH CRAM-MD5");
+ if (resp == 530) { // Authentication is required
+ pr.startTLS();
+ resp = simpleCommand(pr, "AUTH CRAM-MD5");
+ }
+
+ if (resp == 235) // Authentication Succeeded
+ return true;
+
+ if (resp != 334) // server challenge
+ return false;
+
+ try {
+ String t = responseText(pr);
+ byte[] nonce = Base64.decode(t, Base64.NO_WRAP);
+ String hmac = Helper.HMAC("MD5", 64, p.getBytes(), nonce);
+ String hash = Base64.encodeToString((u + " " + hmac).getBytes(), Base64.NO_WRAP);
+ resp = simpleCommand(pr, hash);
+ } catch (NoSuchAlgorithmException ex) {
+ throw new MessagingException("CRAM-MD5", ex);
+ }
+
+ return (resp == 235);
+ }
+
+ private static int simpleCommand(SMTPTransport pr, String command) throws MessagingException {
+ Log.i("SMTP SASL command=" + command);
+ int resp = pr.simpleCommand(command);
+ Log.i("SMTP SASL response=" + pr.getLastServerResponse());
+ return resp;
+ }
+
+ private static String responseText(SMTPTransport pr) {
+ String resp = pr.getLastServerResponse().trim();
+ if (resp.length() > 4)
+ return resp.substring(4);
+ else
+ return "";
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java
index cd6167f395..8cc61c53d6 100644
--- a/app/src/main/java/eu/faircode/email/Helper.java
+++ b/app/src/main/java/eu/faircode/email/Helper.java
@@ -978,6 +978,41 @@ public class Helper {
void onNothingSelected();
}
+ public static String HMAC(String algo, int blocksize, byte[] key, byte[] text) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance(algo);
+
+ if (key.length > blocksize)
+ key = md.digest(key);
+
+ byte[] ipad = new byte[blocksize];
+ byte[] opad = new byte[blocksize];
+
+ for (int i = 0; i < key.length; i++) {
+ ipad[i] = key[i];
+ opad[i] = key[i];
+ }
+
+ for (int i = 0; i < blocksize; i++) {
+ ipad[i] ^= 0x36;
+ opad[i] ^= 0x5c;
+ }
+
+ byte[] digest;
+
+ md.update(ipad);
+ md.update(text);
+ digest = md.digest();
+
+ md.update(opad);
+ md.update(digest);
+ digest = md.digest();
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest)
+ sb.append(String.format("%02x", b));
+ return sb.toString();
+ }
+
// Miscellaneous
static List> chunkList(List list, int size) {
diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java
index 9c484b4810..8412845da7 100644
--- a/app/src/main/java/eu/faircode/email/MailService.java
+++ b/app/src/main/java/eu/faircode/email/MailService.java
@@ -121,6 +121,8 @@ public class MailService implements AutoCloseable {
properties.put("mail.event.scope", "folder");
properties.put("mail.event.executor", executor);
+ properties.put("mail." + protocol + ".sasl.enable", "true");
+ properties.put("mail." + protocol + ".sasl.mechanisms", "CRAM-MD5");
properties.put("mail." + protocol + ".sasl.realm", realm == null ? "" : realm);
properties.put("mail." + protocol + ".auth.ntlm.domain", realm == null ? "" : realm);