2019-07-29 09:17:12 +00:00
|
|
|
package eu.faircode.email;
|
|
|
|
|
2020-10-25 21:20:48 +00:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Copyright 2018-2020 by Marcel Bokhorst (M66B)
|
|
|
|
*/
|
|
|
|
|
2019-07-29 09:17:12 +00:00
|
|
|
import android.content.Context;
|
2019-10-21 11:26:15 +00:00
|
|
|
import android.content.SharedPreferences;
|
2020-02-10 15:35:14 +00:00
|
|
|
import android.security.KeyChain;
|
2019-09-18 14:34:07 +00:00
|
|
|
import android.text.TextUtils;
|
2019-07-29 09:17:12 +00:00
|
|
|
|
2019-12-16 18:09:49 +00:00
|
|
|
import androidx.annotation.NonNull;
|
2019-10-21 11:26:15 +00:00
|
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
|
2020-05-01 19:47:59 +00:00
|
|
|
import com.sun.mail.gimap.GmailSSLProvider;
|
2019-09-18 14:34:07 +00:00
|
|
|
import com.sun.mail.imap.IMAPFolder;
|
2019-07-29 09:17:12 +00:00
|
|
|
import com.sun.mail.imap.IMAPStore;
|
|
|
|
import com.sun.mail.smtp.SMTPTransport;
|
2020-02-12 11:56:19 +00:00
|
|
|
import com.sun.mail.util.MailConnectException;
|
|
|
|
import com.sun.mail.util.SocketConnectException;
|
2019-12-15 18:26:01 +00:00
|
|
|
|
2020-01-31 19:20:44 +00:00
|
|
|
import org.bouncycastle.asn1.DEROctetString;
|
|
|
|
import org.bouncycastle.asn1.x509.Extension;
|
2019-12-15 18:26:01 +00:00
|
|
|
import org.bouncycastle.asn1.x509.GeneralName;
|
2020-01-31 19:20:44 +00:00
|
|
|
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
|
2019-07-29 09:17:12 +00:00
|
|
|
|
2020-10-03 20:00:26 +00:00
|
|
|
import java.io.ByteArrayOutputStream;
|
2019-12-16 15:32:25 +00:00
|
|
|
import java.io.IOException;
|
2020-10-03 20:00:26 +00:00
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.PrintStream;
|
2019-07-29 09:17:12 +00:00
|
|
|
import java.net.Inet4Address;
|
|
|
|
import java.net.Inet6Address;
|
|
|
|
import java.net.InetAddress;
|
2020-03-09 15:32:20 +00:00
|
|
|
import java.net.InterfaceAddress;
|
|
|
|
import java.net.NetworkInterface;
|
2019-12-17 15:11:28 +00:00
|
|
|
import java.net.Socket;
|
2020-08-27 08:15:26 +00:00
|
|
|
import java.net.SocketException;
|
2019-07-29 09:17:12 +00:00
|
|
|
import java.net.UnknownHostException;
|
2019-12-15 18:26:01 +00:00
|
|
|
import java.security.GeneralSecurityException;
|
2019-12-17 15:11:28 +00:00
|
|
|
import java.security.KeyStore;
|
2019-12-15 18:26:01 +00:00
|
|
|
import java.security.NoSuchAlgorithmException;
|
2019-12-19 16:15:29 +00:00
|
|
|
import java.security.Principal;
|
2020-02-20 14:35:10 +00:00
|
|
|
import java.security.PrivateKey;
|
2019-12-15 18:26:01 +00:00
|
|
|
import java.security.cert.CertificateEncodingException;
|
2019-12-18 13:20:16 +00:00
|
|
|
import java.security.cert.CertificateException;
|
2019-12-16 15:32:25 +00:00
|
|
|
import java.security.cert.CertificateParsingException;
|
2019-12-15 18:26:01 +00:00
|
|
|
import java.security.cert.X509Certificate;
|
2019-09-18 14:34:07 +00:00
|
|
|
import java.util.ArrayList;
|
2019-11-08 17:07:06 +00:00
|
|
|
import java.util.Arrays;
|
2019-12-15 18:26:01 +00:00
|
|
|
import java.util.Collection;
|
2019-11-08 17:07:06 +00:00
|
|
|
import java.util.Collections;
|
2020-03-09 15:32:20 +00:00
|
|
|
import java.util.Enumeration;
|
2019-07-29 09:17:12 +00:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.LinkedHashMap;
|
2019-09-18 14:34:07 +00:00
|
|
|
import java.util.List;
|
2019-07-29 09:17:12 +00:00
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Properties;
|
2019-07-30 18:53:37 +00:00
|
|
|
import java.util.concurrent.ExecutorService;
|
2020-01-29 11:44:55 +00:00
|
|
|
import java.util.regex.Pattern;
|
2019-07-29 09:17:12 +00:00
|
|
|
|
2019-09-18 14:34:07 +00:00
|
|
|
import javax.mail.AuthenticationFailedException;
|
2020-10-25 21:20:48 +00:00
|
|
|
import javax.mail.Authenticator;
|
2019-09-18 14:34:07 +00:00
|
|
|
import javax.mail.Folder;
|
2019-07-29 09:17:12 +00:00
|
|
|
import javax.mail.MessagingException;
|
|
|
|
import javax.mail.NoSuchProviderException;
|
|
|
|
import javax.mail.Service;
|
|
|
|
import javax.mail.Session;
|
2019-09-19 15:41:26 +00:00
|
|
|
import javax.mail.Store;
|
2019-12-09 15:32:37 +00:00
|
|
|
import javax.mail.event.StoreListener;
|
2020-08-23 20:50:26 +00:00
|
|
|
import javax.net.SocketFactory;
|
2020-02-09 12:58:16 +00:00
|
|
|
import javax.net.ssl.KeyManager;
|
|
|
|
import javax.net.ssl.KeyManagerFactory;
|
2019-12-17 15:11:28 +00:00
|
|
|
import javax.net.ssl.SSLContext;
|
2020-06-19 16:35:42 +00:00
|
|
|
import javax.net.ssl.SSLException;
|
|
|
|
import javax.net.ssl.SSLHandshakeException;
|
2020-01-28 15:32:26 +00:00
|
|
|
import javax.net.ssl.SSLSocket;
|
2019-12-18 13:20:16 +00:00
|
|
|
import javax.net.ssl.SSLSocketFactory;
|
|
|
|
import javax.net.ssl.TrustManager;
|
2019-12-17 15:11:28 +00:00
|
|
|
import javax.net.ssl.TrustManagerFactory;
|
2019-12-18 13:20:16 +00:00
|
|
|
import javax.net.ssl.X509TrustManager;
|
2019-07-29 09:17:12 +00:00
|
|
|
|
2020-10-25 21:20:48 +00:00
|
|
|
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_GMAIL;
|
|
|
|
import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_OAUTH;
|
|
|
|
|
2020-07-17 08:13:04 +00:00
|
|
|
// IMAP standards: https://imapwiki.org/Specs
|
|
|
|
|
2020-01-29 20:06:45 +00:00
|
|
|
public class EmailService implements AutoCloseable {
|
2019-07-29 09:17:12 +00:00
|
|
|
private Context context;
|
|
|
|
private String protocol;
|
2019-12-17 15:11:28 +00:00
|
|
|
private boolean insecure;
|
2020-06-20 17:08:36 +00:00
|
|
|
private int purpose;
|
2020-01-29 11:44:55 +00:00
|
|
|
private boolean harden;
|
2019-07-30 16:50:42 +00:00
|
|
|
private boolean useip;
|
2020-04-19 13:53:16 +00:00
|
|
|
private String ehlo;
|
2020-10-03 20:00:26 +00:00
|
|
|
private boolean log;
|
2019-07-29 09:17:12 +00:00
|
|
|
private boolean debug;
|
|
|
|
private Properties properties;
|
|
|
|
private Session isession;
|
|
|
|
private Service iservice;
|
2019-12-09 15:32:37 +00:00
|
|
|
private StoreListener listener;
|
2019-07-29 09:17:12 +00:00
|
|
|
|
2019-10-10 11:26:44 +00:00
|
|
|
private ExecutorService executor = Helper.getBackgroundExecutor(0, "mail");
|
2019-07-30 18:53:37 +00:00
|
|
|
|
2020-02-06 12:01:11 +00:00
|
|
|
static final int PURPOSE_CHECK = 1;
|
|
|
|
static final int PURPOSE_USE = 2;
|
|
|
|
static final int PURPOSE_SEARCH = 3;
|
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
static final int ENCRYPTION_SSL = 0;
|
|
|
|
static final int ENCRYPTION_STARTTLS = 1;
|
|
|
|
static final int ENCRYPTION_NONE = 2;
|
|
|
|
|
2020-04-11 19:28:39 +00:00
|
|
|
final static int DEFAULT_CONNECT_TIMEOUT = 20; // seconds
|
2020-03-08 18:11:39 +00:00
|
|
|
|
2020-07-08 13:37:38 +00:00
|
|
|
private final static int SEARCH_TIMEOUT = 90 * 1000; // milliseconds
|
2020-01-21 10:09:57 +00:00
|
|
|
private final static int FETCH_SIZE = 1024 * 1024; // bytes, default 16K
|
2020-08-22 10:32:23 +00:00
|
|
|
private final static int POOL_TIMEOUT = 45 * 1000; // milliseconds, default 45 sec
|
2019-07-29 14:42:17 +00:00
|
|
|
|
|
|
|
private static final int APPEND_BUFFER_SIZE = 4 * 1024 * 1024; // bytes
|
|
|
|
|
2020-06-02 13:25:14 +00:00
|
|
|
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html#protocols
|
2020-01-29 11:44:55 +00:00
|
|
|
private static final List<String> SSL_PROTOCOL_BLACKLIST = Collections.unmodifiableList(Arrays.asList(
|
|
|
|
"SSLv2", "SSLv3", "TLSv1", "TLSv1.1"
|
|
|
|
));
|
|
|
|
|
2020-06-02 13:25:14 +00:00
|
|
|
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html#cipher-suites
|
2020-03-21 19:06:31 +00:00
|
|
|
private static final Pattern SSL_CIPHER_BLACKLIST =
|
|
|
|
Pattern.compile(".*(_DES|DH_|DSS|EXPORT|MD5|NULL|RC4|TLS_FALLBACK_SCSV).*");
|
|
|
|
|
2020-05-24 16:29:07 +00:00
|
|
|
// TLS_FALLBACK_SCSV https://tools.ietf.org/html/rfc7507
|
|
|
|
// TLS_EMPTY_RENEGOTIATION_INFO_SCSV https://tools.ietf.org/html/rfc5746
|
2020-05-24 16:20:14 +00:00
|
|
|
|
2020-01-29 20:06:45 +00:00
|
|
|
private EmailService() {
|
2020-01-29 11:44:55 +00:00
|
|
|
// Prevent instantiation
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
EmailService(Context context, String protocol, String realm, int encryption, boolean insecure, boolean debug) throws NoSuchProviderException {
|
|
|
|
this(context, protocol, realm, encryption, insecure, PURPOSE_USE, debug);
|
2020-02-06 12:01:11 +00:00
|
|
|
}
|
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
EmailService(Context context, String protocol, String realm, int encryption, boolean insecure, int purpose, boolean debug) throws NoSuchProviderException {
|
2019-07-29 14:42:17 +00:00
|
|
|
this.context = context.getApplicationContext();
|
2019-07-29 09:17:12 +00:00
|
|
|
this.protocol = protocol;
|
2019-12-17 15:11:28 +00:00
|
|
|
this.insecure = insecure;
|
2020-06-20 17:08:36 +00:00
|
|
|
this.purpose = purpose;
|
2019-07-29 09:17:12 +00:00
|
|
|
this.debug = debug;
|
2019-10-03 09:39:14 +00:00
|
|
|
|
2019-07-30 16:53:18 +00:00
|
|
|
properties = MessageHelper.getSessionProperties();
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2019-10-21 11:26:15 +00:00
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
2020-10-03 20:00:26 +00:00
|
|
|
this.log = prefs.getBoolean("protocol", false);
|
2020-01-29 11:44:55 +00:00
|
|
|
this.harden = prefs.getBoolean("ssl_harden", false);
|
|
|
|
|
2020-07-12 09:19:03 +00:00
|
|
|
boolean auth_plain = prefs.getBoolean("auth_plain", true);
|
|
|
|
boolean auth_login = prefs.getBoolean("auth_login", true);
|
2020-06-02 13:47:27 +00:00
|
|
|
boolean auth_sasl = prefs.getBoolean("auth_sasl", true);
|
2020-07-12 09:19:03 +00:00
|
|
|
Log.i("Authenticate plain=" + auth_plain + " login=" + auth_login + " sasl=" + auth_sasl);
|
2020-05-30 14:17:54 +00:00
|
|
|
|
2019-12-11 06:42:38 +00:00
|
|
|
properties.put("mail.event.scope", "folder");
|
2019-07-30 18:53:37 +00:00
|
|
|
properties.put("mail.event.executor", executor);
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2020-07-12 09:19:03 +00:00
|
|
|
if (!auth_plain)
|
|
|
|
properties.put("mail." + protocol + ".auth.plain.disable", "true");
|
|
|
|
if (!auth_login)
|
|
|
|
properties.put("mail." + protocol + ".auth.login.disable", "true");
|
|
|
|
|
2020-10-25 10:18:18 +00:00
|
|
|
properties.put("mail." + protocol + ".sasl.enable", Boolean.toString(auth_sasl));
|
|
|
|
properties.put("mail." + protocol + ".sasl.mechanisms", "CRAM-MD5");
|
|
|
|
properties.put("mail." + protocol + ".sasl.realm", realm == null ? "" : realm);
|
2019-09-29 18:25:01 +00:00
|
|
|
properties.put("mail." + protocol + ".auth.ntlm.domain", realm == null ? "" : realm);
|
|
|
|
|
2020-02-06 12:01:11 +00:00
|
|
|
// writetimeout: one thread overhead
|
2020-03-08 18:11:39 +00:00
|
|
|
int timeout = prefs.getInt("timeout", DEFAULT_CONNECT_TIMEOUT) * 1000;
|
|
|
|
Log.i("Timeout=" + timeout);
|
2020-02-06 19:55:44 +00:00
|
|
|
if (purpose == PURPOSE_SEARCH) {
|
2020-03-08 18:11:39 +00:00
|
|
|
properties.put("mail." + protocol + ".connectiontimeout", Integer.toString(timeout));
|
2020-02-06 12:01:11 +00:00
|
|
|
properties.put("mail." + protocol + ".writetimeout", Integer.toString(SEARCH_TIMEOUT));
|
|
|
|
properties.put("mail." + protocol + ".timeout", Integer.toString(SEARCH_TIMEOUT));
|
|
|
|
} else {
|
2020-03-08 18:11:39 +00:00
|
|
|
properties.put("mail." + protocol + ".connectiontimeout", Integer.toString(timeout));
|
|
|
|
properties.put("mail." + protocol + ".writetimeout", Integer.toString(timeout * 2));
|
|
|
|
properties.put("mail." + protocol + ".timeout", Integer.toString(timeout * 2));
|
2020-02-06 12:01:11 +00:00
|
|
|
}
|
2019-10-19 08:05:43 +00:00
|
|
|
|
2019-10-03 09:39:14 +00:00
|
|
|
if (debug && BuildConfig.DEBUG)
|
|
|
|
properties.put("mail.debug.auth", "true");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
boolean starttls = (encryption == ENCRYPTION_STARTTLS);
|
|
|
|
|
2020-08-23 20:50:26 +00:00
|
|
|
if (encryption == ENCRYPTION_NONE) {
|
|
|
|
properties.put("mail." + protocol + ".ssl.enable", "false");
|
|
|
|
properties.put("mail." + protocol + ".socketFactory", new SocketFactoryService());
|
|
|
|
}
|
|
|
|
|
2019-09-19 15:41:26 +00:00
|
|
|
if ("pop3".equals(protocol) || "pop3s".equals(protocol)) {
|
|
|
|
// https://javaee.github.io/javamail/docs/api/com/sun/mail/pop3/package-summary.html#properties
|
|
|
|
properties.put("mail.pop3s.starttls.enable", "false");
|
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
properties.put("mail.pop3.starttls.enable", Boolean.toString(starttls));
|
|
|
|
properties.put("mail.pop3.starttls.required", Boolean.toString(starttls && !insecure));
|
2019-09-19 15:41:26 +00:00
|
|
|
|
2020-05-01 19:47:59 +00:00
|
|
|
} else if ("imap".equals(protocol) || "imaps".equals(protocol) || "gimaps".equals(protocol)) {
|
2019-07-29 14:42:17 +00:00
|
|
|
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail.imaps.starttls.enable", "false");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
properties.put("mail.imap.starttls.enable", Boolean.toString(starttls));
|
|
|
|
properties.put("mail.imap.starttls.required", Boolean.toString(starttls && !insecure));
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2020-08-10 17:56:20 +00:00
|
|
|
properties.put("mail." + protocol + ".separatestoreconnection", "true");
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".connectionpool.debug", "true");
|
2020-08-10 17:56:20 +00:00
|
|
|
properties.put("mail." + protocol + ".connectionpoolsize", Integer.toString(2));
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".connectionpooltimeout", Integer.toString(POOL_TIMEOUT));
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".finalizecleanclose", "false");
|
2019-10-13 07:54:13 +00:00
|
|
|
//properties.put("mail." + protocol + ".closefoldersonstorefailure", "false");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
|
|
|
// https://tools.ietf.org/html/rfc4978
|
|
|
|
// https://docs.oracle.com/javase/8/docs/api/java/util/zip/Deflater.html
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".compress.enable", "true");
|
|
|
|
//properties.put("mail.imaps.compress.level", "-1");
|
|
|
|
//properties.put("mail.imaps.compress.strategy", "0");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".throwsearchexception", "true");
|
|
|
|
properties.put("mail." + protocol + ".fetchsize", Integer.toString(FETCH_SIZE));
|
|
|
|
properties.put("mail." + protocol + ".peek", "true");
|
|
|
|
properties.put("mail." + protocol + ".appendbuffersize", Integer.toString(APPEND_BUFFER_SIZE));
|
2019-07-29 14:42:17 +00:00
|
|
|
|
|
|
|
} else if ("smtp".equals(protocol) || "smtps".equals(protocol)) {
|
|
|
|
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail.smtps.starttls.enable", "false");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2020-08-23 07:28:10 +00:00
|
|
|
properties.put("mail.smtp.starttls.enable", Boolean.toString(starttls));
|
|
|
|
properties.put("mail.smtp.starttls.required", Boolean.toString(starttls && !insecure));
|
2019-07-29 14:42:17 +00:00
|
|
|
|
2019-07-30 16:53:18 +00:00
|
|
|
properties.put("mail." + protocol + ".auth", "true");
|
2019-07-29 14:42:17 +00:00
|
|
|
|
|
|
|
} else
|
|
|
|
throw new NoSuchProviderException(protocol);
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void setPartialFetch(boolean enabled) {
|
2019-10-09 12:01:26 +00:00
|
|
|
properties.put("mail." + protocol + ".partialfetch", Boolean.toString(enabled));
|
|
|
|
}
|
|
|
|
|
|
|
|
void setIgnoreBodyStructureSize(boolean enabled) {
|
|
|
|
properties.put("mail." + protocol + ".ignorebodystructuresize", Boolean.toString(enabled));
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-19 13:53:16 +00:00
|
|
|
void setUseIp(boolean enabled, String host) {
|
|
|
|
this.useip = enabled;
|
|
|
|
this.ehlo = host;
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2019-09-25 11:26:56 +00:00
|
|
|
void setLeaveOnServer(boolean keep) {
|
|
|
|
properties.put("mail." + protocol + ".rsetbeforequit", Boolean.toString(keep));
|
|
|
|
}
|
|
|
|
|
2020-04-13 07:11:25 +00:00
|
|
|
void setUnicode(boolean value) {
|
|
|
|
properties.put("mail.mime.allowutf8", Boolean.toString(value));
|
|
|
|
}
|
|
|
|
|
2020-09-05 13:52:28 +00:00
|
|
|
// https://tools.ietf.org/html/rfc3461
|
|
|
|
void setDsnNotify(String what) {
|
|
|
|
properties.put("mail." + protocol + ".dsn.notify", what);
|
|
|
|
}
|
|
|
|
|
2019-12-09 15:32:37 +00:00
|
|
|
void setListener(StoreListener listener) {
|
|
|
|
this.listener = listener;
|
|
|
|
}
|
|
|
|
|
2019-07-29 09:17:12 +00:00
|
|
|
public void connect(EntityAccount account) throws MessagingException {
|
2020-10-25 21:20:48 +00:00
|
|
|
connect(
|
2020-02-09 12:58:16 +00:00
|
|
|
account.host, account.port,
|
|
|
|
account.auth_type, account.provider,
|
|
|
|
account.user, account.password,
|
2020-10-25 21:20:48 +00:00
|
|
|
new ServiceAuthenticator.IAuthenticated() {
|
|
|
|
@Override
|
|
|
|
public void onPasswordChanged(String newPassword) {
|
|
|
|
DB db = DB.getInstance(context);
|
|
|
|
int count = db.account().setAccountPassword(account.id, newPassword);
|
|
|
|
EntityLog.log(context, account.name + " token refreshed=" + count);
|
|
|
|
}
|
|
|
|
},
|
2020-02-10 15:35:14 +00:00
|
|
|
account.certificate_alias, account.fingerprint);
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void connect(EntityIdentity identity) throws MessagingException {
|
2020-10-25 21:20:48 +00:00
|
|
|
connect(
|
2020-02-09 12:58:16 +00:00
|
|
|
identity.host, identity.port,
|
|
|
|
identity.auth_type, identity.provider,
|
|
|
|
identity.user, identity.password,
|
2020-10-25 21:20:48 +00:00
|
|
|
new ServiceAuthenticator.IAuthenticated() {
|
|
|
|
@Override
|
|
|
|
public void onPasswordChanged(String newPassword) {
|
|
|
|
DB db = DB.getInstance(context);
|
|
|
|
int count = db.identity().setIdentityPassword(identity.id, newPassword);
|
|
|
|
EntityLog.log(context, identity.email + " token refreshed=" + count);
|
|
|
|
|
|
|
|
}
|
|
|
|
},
|
2020-02-10 15:35:14 +00:00
|
|
|
identity.certificate_alias, identity.fingerprint);
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 21:20:48 +00:00
|
|
|
public void connect(
|
|
|
|
String host, int port,
|
|
|
|
int auth, String provider, String user, String password,
|
|
|
|
String certificate, String fingerprint) throws MessagingException {
|
|
|
|
connect(host, port, auth, provider, user, password, null, certificate, fingerprint);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void connect(
|
2020-01-15 07:58:31 +00:00
|
|
|
String host, int port,
|
|
|
|
int auth, String provider, String user, String password,
|
2020-10-25 21:20:48 +00:00
|
|
|
ServiceAuthenticator.IAuthenticated intf,
|
2020-02-10 15:35:14 +00:00
|
|
|
String certificate, String fingerprint) throws MessagingException {
|
2019-12-19 10:53:37 +00:00
|
|
|
SSLSocketFactoryService factory = null;
|
2019-12-17 15:11:28 +00:00
|
|
|
try {
|
2020-02-20 14:35:10 +00:00
|
|
|
PrivateKey key = null;
|
|
|
|
X509Certificate[] chain = null;
|
2020-02-10 15:35:14 +00:00
|
|
|
if (certificate != null) {
|
|
|
|
Log.i("Get client certificate alias=" + certificate);
|
|
|
|
try {
|
2020-02-20 14:35:10 +00:00
|
|
|
key = KeyChain.getPrivateKey(context, certificate);
|
|
|
|
chain = KeyChain.getCertificateChain(context, certificate);
|
2020-02-10 15:35:14 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-20 14:35:10 +00:00
|
|
|
factory = new SSLSocketFactoryService(host, insecure, harden, key, chain, fingerprint);
|
2019-12-17 15:11:28 +00:00
|
|
|
properties.put("mail." + protocol + ".ssl.socketFactory", factory);
|
|
|
|
properties.put("mail." + protocol + ".socketFactory.fallback", "false");
|
2019-12-18 13:20:16 +00:00
|
|
|
properties.put("mail." + protocol + ".ssl.checkserveridentity", "false");
|
2019-12-17 15:11:28 +00:00
|
|
|
} catch (GeneralSecurityException ex) {
|
2019-12-18 13:20:16 +00:00
|
|
|
properties.put("mail." + protocol + ".ssl.checkserveridentity", Boolean.toString(!insecure));
|
|
|
|
if (insecure)
|
|
|
|
properties.put("mail." + protocol + ".ssl.trust", "*");
|
2019-12-19 10:53:37 +00:00
|
|
|
Log.e("Trust issues", ex);
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
2020-10-25 21:20:48 +00:00
|
|
|
properties.put("mail." + protocol + ".forcepasswordrefresh", "true");
|
|
|
|
ServiceAuthenticator authenticator = new ServiceAuthenticator(context, auth, provider, user, password, intf);
|
|
|
|
|
2019-07-29 09:17:12 +00:00
|
|
|
try {
|
2019-12-20 16:20:06 +00:00
|
|
|
if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH)
|
2019-09-18 14:34:07 +00:00
|
|
|
properties.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2");
|
|
|
|
|
2020-10-25 21:20:48 +00:00
|
|
|
if (auth == AUTH_TYPE_OAUTH && "imap.mail.yahoo.com".equals(host))
|
|
|
|
properties.put("mail." + protocol + ".yahoo.guid", "FAIRMAIL_V1");
|
|
|
|
|
|
|
|
connect(host, port, auth, user, authenticator, factory);
|
2019-09-18 14:34:07 +00:00
|
|
|
} catch (AuthenticationFailedException ex) {
|
2020-10-25 21:20:48 +00:00
|
|
|
if (auth == AUTH_TYPE_GMAIL || auth == AUTH_TYPE_OAUTH) {
|
2019-09-18 14:34:07 +00:00
|
|
|
try {
|
2020-10-26 08:31:10 +00:00
|
|
|
authenticator.refreshToken(true);
|
2020-10-25 21:20:48 +00:00
|
|
|
connect(host, port, auth, user, authenticator, factory);
|
2019-09-24 13:34:39 +00:00
|
|
|
} catch (Exception ex1) {
|
2019-09-18 14:34:07 +00:00
|
|
|
Log.e(ex1);
|
2019-09-24 13:34:39 +00:00
|
|
|
throw new AuthenticationFailedException(ex.getMessage(), ex1);
|
2019-09-18 14:34:07 +00:00
|
|
|
}
|
2020-08-09 11:46:46 +00:00
|
|
|
} else if (purpose == PURPOSE_CHECK) {
|
|
|
|
String msg = ex.getMessage();
|
|
|
|
if (msg != null)
|
|
|
|
msg = msg.trim();
|
|
|
|
if (TextUtils.isEmpty(msg))
|
|
|
|
throw ex;
|
|
|
|
throw new AuthenticationFailedException(
|
|
|
|
context.getString(R.string.title_service_auth, msg),
|
|
|
|
ex.getNextException());
|
2019-12-20 16:20:06 +00:00
|
|
|
} else
|
2019-09-18 14:34:07 +00:00
|
|
|
throw ex;
|
2020-02-12 11:56:19 +00:00
|
|
|
} catch (MailConnectException ex) {
|
|
|
|
if (ConnectionHelper.vpnActive(context)) {
|
2020-08-09 11:54:12 +00:00
|
|
|
MailConnectException mex = new MailConnectException(
|
|
|
|
new SocketConnectException(
|
|
|
|
context.getString(R.string.title_service_vpn),
|
|
|
|
new Exception(),
|
|
|
|
ex.getHost(),
|
|
|
|
ex.getPort(),
|
|
|
|
ex.getConnectionTimeout()));
|
2020-02-12 11:56:19 +00:00
|
|
|
mex.setNextException(ex.getNextException());
|
|
|
|
throw mex;
|
|
|
|
} else
|
|
|
|
throw ex;
|
2020-03-11 13:12:51 +00:00
|
|
|
} catch (MessagingException ex) {
|
2020-06-20 17:08:36 +00:00
|
|
|
if (purpose == PURPOSE_CHECK) {
|
|
|
|
if (port == 995 && !("pop3".equals(protocol) || "pop3s".equals(protocol)))
|
|
|
|
throw new MessagingException(context.getString(R.string.title_service_port), ex);
|
|
|
|
else if (ex.getMessage() != null &&
|
|
|
|
ex.getMessage().contains("Got bad greeting"))
|
|
|
|
throw new MessagingException(context.getString(R.string.title_service_protocol), ex);
|
|
|
|
else if (ex.getCause() instanceof SSLException &&
|
|
|
|
ex.getCause().getMessage() != null &&
|
|
|
|
ex.getCause().getMessage().contains("Unable to parse TLS packet header"))
|
|
|
|
throw new MessagingException(context.getString(R.string.title_service_protocol), ex);
|
|
|
|
else if (ex.getCause() instanceof SSLHandshakeException)
|
|
|
|
throw new MessagingException(context.getString(R.string.title_service_protocol), ex);
|
|
|
|
else
|
|
|
|
throw ex;
|
|
|
|
} else
|
2020-03-11 13:12:51 +00:00
|
|
|
throw ex;
|
2020-01-15 07:58:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void connect(
|
2020-06-30 20:50:56 +00:00
|
|
|
String host, int port, int auth,
|
2020-10-25 21:20:48 +00:00
|
|
|
String user, Authenticator authenticator,
|
2020-01-15 07:58:31 +00:00
|
|
|
SSLSocketFactoryService factory) throws MessagingException {
|
2020-03-28 07:05:36 +00:00
|
|
|
InetAddress main = null;
|
2020-06-30 20:50:56 +00:00
|
|
|
boolean require_id = (purpose == PURPOSE_CHECK &&
|
|
|
|
auth == AUTH_TYPE_OAUTH &&
|
|
|
|
"outlook.office365.com".equals(host));
|
|
|
|
Log.i("Require ID=" + require_id);
|
2020-01-15 07:58:31 +00:00
|
|
|
try {
|
|
|
|
//if (BuildConfig.DEBUG)
|
|
|
|
// throw new MailConnectException(
|
|
|
|
// new SocketConnectException("Debug", new IOException("Test"), host, port, 0));
|
|
|
|
|
2020-03-28 07:05:36 +00:00
|
|
|
main = InetAddress.getByName(host);
|
2020-10-27 12:22:23 +00:00
|
|
|
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
boolean prefer_ip4 = prefs.getBoolean("prefer_ip4", false);
|
|
|
|
if (prefer_ip4 && main instanceof Inet6Address) {
|
|
|
|
for (InetAddress iaddr : InetAddress.getAllByName(host))
|
|
|
|
if (iaddr instanceof Inet4Address) {
|
|
|
|
main = iaddr;
|
|
|
|
EntityLog.log(context, "Preferring=" + main);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-28 07:05:36 +00:00
|
|
|
EntityLog.log(context, "Connecting to " + main);
|
2020-10-25 21:20:48 +00:00
|
|
|
_connect(main, port, require_id, user, authenticator, factory);
|
2020-03-28 07:05:36 +00:00
|
|
|
} catch (UnknownHostException ex) {
|
|
|
|
throw new MessagingException(ex.getMessage(), ex);
|
2020-01-15 07:58:31 +00:00
|
|
|
} catch (MessagingException ex) {
|
|
|
|
boolean ioError = false;
|
|
|
|
Throwable ce = ex;
|
|
|
|
while (ce != null) {
|
|
|
|
if (factory != null && ce instanceof CertificateException)
|
2020-01-31 19:20:44 +00:00
|
|
|
throw new UntrustedException(factory.getFingerPrintSelect(), ex);
|
2020-01-15 07:58:31 +00:00
|
|
|
if (ce instanceof IOException)
|
|
|
|
ioError = true;
|
|
|
|
ce = ce.getCause();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ioError) {
|
2020-03-30 11:06:47 +00:00
|
|
|
EntityLog.log(context, "Connect ex=" +
|
|
|
|
ex.getClass().getName() + ":" + ex.getMessage());
|
2020-01-15 07:58:31 +00:00
|
|
|
try {
|
|
|
|
// Some devices resolve IPv6 addresses while not having IPv6 connectivity
|
|
|
|
InetAddress[] iaddrs = InetAddress.getAllByName(host);
|
2020-03-08 10:24:18 +00:00
|
|
|
boolean ip4 = (main instanceof Inet4Address);
|
|
|
|
boolean ip6 = (main instanceof Inet6Address);
|
2020-03-09 15:32:20 +00:00
|
|
|
|
|
|
|
boolean has4 = false;
|
|
|
|
boolean has6 = false;
|
|
|
|
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
|
|
|
while (interfaces != null && interfaces.hasMoreElements()) {
|
|
|
|
NetworkInterface ni = interfaces.nextElement();
|
|
|
|
for (InterfaceAddress iaddr : ni.getInterfaceAddresses()) {
|
|
|
|
InetAddress addr = iaddr.getAddress();
|
2020-03-10 15:30:53 +00:00
|
|
|
boolean local = (addr.isLoopbackAddress() || addr.isLinkLocalAddress());
|
|
|
|
EntityLog.log(context, "Interface=" + ni + " addr=" + addr + " local=" + local);
|
|
|
|
if (!local)
|
2020-03-09 15:32:20 +00:00
|
|
|
if (addr instanceof Inet4Address)
|
|
|
|
has4 = true;
|
|
|
|
else if (addr instanceof Inet6Address)
|
|
|
|
has6 = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 15:30:53 +00:00
|
|
|
EntityLog.log(context, "Address main=" + main +
|
|
|
|
" count=" + iaddrs.length +
|
2020-03-09 15:32:20 +00:00
|
|
|
" ip4=" + ip4 + "/" + has4 +
|
2020-03-15 09:14:43 +00:00
|
|
|
" ip6=" + ip6 + "/" + has6);
|
2020-03-10 15:30:53 +00:00
|
|
|
|
|
|
|
for (InetAddress iaddr : iaddrs) {
|
|
|
|
EntityLog.log(context, "Address resolved=" + iaddr);
|
|
|
|
|
|
|
|
if (iaddr.equals(main))
|
|
|
|
continue;
|
|
|
|
|
2020-03-14 17:53:16 +00:00
|
|
|
if (iaddr instanceof Inet4Address) {
|
2020-03-15 09:14:43 +00:00
|
|
|
if (!has4 || ip4)
|
2020-03-14 17:53:16 +00:00
|
|
|
continue;
|
|
|
|
ip4 = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iaddr instanceof Inet6Address) {
|
2020-03-15 09:14:43 +00:00
|
|
|
if (!has6 || ip6)
|
2020-03-14 17:53:16 +00:00
|
|
|
continue;
|
|
|
|
ip6 = true;
|
2020-03-10 15:30:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2020-03-28 07:05:36 +00:00
|
|
|
EntityLog.log(context, "Falling back to " + iaddr);
|
2020-10-25 21:20:48 +00:00
|
|
|
_connect(iaddr, port, require_id, user, authenticator, factory);
|
2020-03-10 15:30:53 +00:00
|
|
|
return;
|
|
|
|
} catch (MessagingException ex1) {
|
2020-03-30 11:06:47 +00:00
|
|
|
ex = ex1;
|
|
|
|
EntityLog.log(context, "Fallback ex=" +
|
|
|
|
ex1.getClass().getName() + ":" + ex1.getMessage());
|
2020-03-08 10:24:18 +00:00
|
|
|
}
|
2020-03-10 15:30:53 +00:00
|
|
|
}
|
2020-03-30 11:06:47 +00:00
|
|
|
} catch (IOException ex1) {
|
|
|
|
throw new MessagingException(ex1.getMessage(), ex1);
|
2020-01-15 07:58:31 +00:00
|
|
|
}
|
2019-07-30 15:33:20 +00:00
|
|
|
}
|
2019-07-29 14:23:25 +00:00
|
|
|
|
|
|
|
throw ex;
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-18 13:20:16 +00:00
|
|
|
private void _connect(
|
2020-06-30 20:50:56 +00:00
|
|
|
InetAddress address, int port, boolean require_id,
|
2020-10-25 21:20:48 +00:00
|
|
|
String user, Authenticator authenticator,
|
2019-12-18 13:20:16 +00:00
|
|
|
SSLSocketFactoryService factory) throws MessagingException {
|
2020-10-25 21:20:48 +00:00
|
|
|
isession = Session.getInstance(properties, authenticator);
|
2020-10-03 20:00:26 +00:00
|
|
|
|
|
|
|
isession.setDebug(debug || log);
|
|
|
|
if (debug || log)
|
|
|
|
isession.setDebugOut(new PrintStream(new OutputStream() {
|
|
|
|
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void write(int b) {
|
2020-10-04 10:07:12 +00:00
|
|
|
try {
|
|
|
|
if (((char) b) == '\n') {
|
|
|
|
String line = bos.toString();
|
|
|
|
if (!line.endsWith("ignoring socket timeout"))
|
|
|
|
if (log)
|
|
|
|
EntityLog.log(context, user + " " + line);
|
|
|
|
else
|
|
|
|
android.util.Log.i("javamail", user + " " + line);
|
|
|
|
bos.reset();
|
|
|
|
} else
|
|
|
|
bos.write(b);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
2020-10-03 20:00:26 +00:00
|
|
|
}
|
2020-10-04 10:07:12 +00:00
|
|
|
}, true));
|
2020-10-03 20:00:26 +00:00
|
|
|
|
2020-01-15 07:58:31 +00:00
|
|
|
//System.setProperty("mail.socket.debug", Boolean.toString(debug));
|
2020-05-01 19:47:59 +00:00
|
|
|
isession.addProvider(new GmailSSLProvider());
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2020-01-15 07:58:31 +00:00
|
|
|
if ("pop3".equals(protocol) || "pop3s".equals(protocol)) {
|
|
|
|
iservice = isession.getStore(protocol);
|
2020-10-25 21:20:48 +00:00
|
|
|
iservice.connect(address.getHostAddress(), port, user, null);
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2020-05-01 19:47:59 +00:00
|
|
|
} else if ("imap".equals(protocol) || "imaps".equals(protocol) || "gimaps".equals(protocol)) {
|
2020-01-15 07:58:31 +00:00
|
|
|
iservice = isession.getStore(protocol);
|
|
|
|
if (listener != null)
|
|
|
|
((IMAPStore) iservice).addStoreListener(listener);
|
2020-10-25 21:20:48 +00:00
|
|
|
iservice.connect(address.getHostAddress(), port, user, null);
|
2020-01-15 07:58:31 +00:00
|
|
|
|
|
|
|
// https://www.ietf.org/rfc/rfc2971.txt
|
|
|
|
IMAPStore istore = (IMAPStore) getStore();
|
|
|
|
if (istore.hasCapability("ID"))
|
|
|
|
try {
|
|
|
|
Map<String, String> id = new LinkedHashMap<>();
|
|
|
|
id.put("name", context.getString(R.string.app_name));
|
|
|
|
id.put("version", BuildConfig.VERSION_NAME);
|
|
|
|
Map<String, String> sid = istore.id(id);
|
|
|
|
if (sid != null) {
|
|
|
|
Map<String, String> crumb = new HashMap<>();
|
|
|
|
for (String key : sid.keySet()) {
|
|
|
|
crumb.put(key, sid.get(key));
|
|
|
|
EntityLog.log(context, "Server " + key + "=" + sid.get(key));
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
2020-01-15 07:58:31 +00:00
|
|
|
Log.breadcrumb("server", crumb);
|
2019-12-18 13:20:16 +00:00
|
|
|
}
|
2020-01-15 07:58:31 +00:00
|
|
|
} catch (MessagingException ex) {
|
|
|
|
Log.w(ex);
|
2020-07-01 07:53:30 +00:00
|
|
|
// Check for 'User is authenticated but not connected'
|
2020-06-30 20:50:56 +00:00
|
|
|
if (require_id)
|
2020-06-30 19:15:25 +00:00
|
|
|
throw ex;
|
2020-01-15 07:58:31 +00:00
|
|
|
}
|
2019-07-30 16:50:42 +00:00
|
|
|
|
2020-01-15 07:58:31 +00:00
|
|
|
} else if ("smtp".equals(protocol) || "smtps".equals(protocol)) {
|
2020-04-08 12:59:33 +00:00
|
|
|
// https://tools.ietf.org/html/rfc5321#section-4.1.3
|
2020-10-10 05:49:18 +00:00
|
|
|
String hdomain = getDefaultEhlo();
|
2020-04-08 12:59:33 +00:00
|
|
|
String haddr = (address instanceof Inet4Address ? "[127.0.0.1]" : "[IPv6:::1]");
|
2020-01-15 07:58:31 +00:00
|
|
|
|
2020-04-19 13:53:16 +00:00
|
|
|
properties.put("mail." + protocol + ".localhost",
|
|
|
|
ehlo == null ? (useip ? haddr : hdomain) : ehlo);
|
2020-04-08 12:59:33 +00:00
|
|
|
Log.i("Using localhost=" + properties.getProperty("mail." + protocol + ".localhost"));
|
2020-01-15 07:58:31 +00:00
|
|
|
|
|
|
|
iservice = isession.getTransport(protocol);
|
|
|
|
try {
|
2020-10-25 21:20:48 +00:00
|
|
|
iservice.connect(address.getHostAddress(), port, user, null);
|
2020-01-15 07:58:31 +00:00
|
|
|
} catch (MessagingException ex) {
|
2020-08-23 07:28:10 +00:00
|
|
|
if (ehlo == null && ConnectionHelper.isSyntacticallyInvalid(ex)) {
|
2020-04-08 12:59:33 +00:00
|
|
|
properties.put("mail." + protocol + ".localhost", useip ? hdomain : haddr);
|
|
|
|
Log.i("Fallback localhost=" + properties.getProperty("mail." + protocol + ".localhost"));
|
|
|
|
try {
|
2020-10-25 21:20:48 +00:00
|
|
|
iservice.connect(address.getHostAddress(), port, user, null);
|
2020-04-08 12:59:33 +00:00
|
|
|
} catch (MessagingException ex1) {
|
|
|
|
if (ConnectionHelper.isSyntacticallyInvalid(ex1))
|
|
|
|
Log.e("Used localhost=" + haddr + "/" + hdomain);
|
|
|
|
throw ex1;
|
|
|
|
}
|
2020-01-15 07:58:31 +00:00
|
|
|
} else
|
|
|
|
throw ex;
|
2020-01-12 18:10:10 +00:00
|
|
|
}
|
2020-01-15 07:58:31 +00:00
|
|
|
} else
|
|
|
|
throw new NoSuchProviderException(protocol);
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2020-10-10 05:49:18 +00:00
|
|
|
static String getDefaultEhlo() {
|
|
|
|
String[] c = BuildConfig.APPLICATION_ID.split("\\.");
|
|
|
|
Collections.reverse(Arrays.asList(c));
|
|
|
|
return TextUtils.join(".", c);
|
|
|
|
}
|
|
|
|
|
2019-09-18 14:34:07 +00:00
|
|
|
List<EntityFolder> getFolders() throws MessagingException {
|
|
|
|
List<EntityFolder> folders = new ArrayList<>();
|
|
|
|
|
|
|
|
for (Folder ifolder : getStore().getDefaultFolder().list("*")) {
|
|
|
|
String fullName = ifolder.getFullName();
|
|
|
|
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
|
|
|
String type = EntityFolder.getType(attrs, fullName, true);
|
|
|
|
Log.i(fullName + " attrs=" + TextUtils.join(" ", attrs) + " type=" + type);
|
|
|
|
|
2020-05-07 07:31:32 +00:00
|
|
|
if (type != null)
|
|
|
|
folders.add(new EntityFolder(fullName, type));
|
2019-09-18 14:34:07 +00:00
|
|
|
}
|
|
|
|
|
2020-05-07 07:31:32 +00:00
|
|
|
EntityFolder.guessTypes(folders, getStore().getDefaultFolder().getSeparator());
|
2019-09-18 14:34:07 +00:00
|
|
|
|
|
|
|
boolean inbox = false;
|
|
|
|
for (EntityFolder folder : folders)
|
2020-08-20 18:06:36 +00:00
|
|
|
if (EntityFolder.INBOX.equals(folder.type)) {
|
2019-09-18 14:34:07 +00:00
|
|
|
inbox = true;
|
2020-08-20 18:06:36 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-09-18 14:34:07 +00:00
|
|
|
|
2020-08-20 18:06:36 +00:00
|
|
|
if (!inbox)
|
|
|
|
throw new IllegalArgumentException(context.getString(R.string.title_setup_no_inbox));
|
2019-09-18 14:34:07 +00:00
|
|
|
|
|
|
|
return folders;
|
|
|
|
}
|
|
|
|
|
2019-09-19 15:41:26 +00:00
|
|
|
Store getStore() {
|
|
|
|
return (Store) iservice;
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SMTPTransport getTransport() {
|
2019-07-30 16:53:18 +00:00
|
|
|
return (SMTPTransport) iservice;
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
|
2020-07-03 07:57:43 +00:00
|
|
|
Long getMaxSize() throws MessagingException {
|
2020-07-03 08:04:09 +00:00
|
|
|
// https://support.google.com/mail/answer/6584#limit
|
|
|
|
|
2020-07-03 07:57:43 +00:00
|
|
|
String size;
|
|
|
|
if (iservice instanceof SMTPTransport) {
|
|
|
|
// https://tools.ietf.org/html/rfc1870
|
|
|
|
size = getTransport().getExtensionParameter("SIZE");
|
|
|
|
} else if (iservice instanceof IMAPStore) {
|
|
|
|
// https://tools.ietf.org/html/rfc7889
|
|
|
|
size = ((IMAPStore) iservice).getCapability("APPENDLIMIT");
|
|
|
|
} else
|
|
|
|
return null;
|
|
|
|
|
2020-07-02 12:26:41 +00:00
|
|
|
if (!TextUtils.isEmpty(size) && TextUtils.isDigitsOnly(size)) {
|
|
|
|
long s = Long.parseLong(size);
|
|
|
|
if (s != 0) // Not infinite
|
|
|
|
return s;
|
|
|
|
}
|
2020-07-01 21:34:53 +00:00
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-09-19 15:41:26 +00:00
|
|
|
boolean hasCapability(String capability) throws MessagingException {
|
|
|
|
Store store = getStore();
|
|
|
|
if (store instanceof IMAPStore)
|
|
|
|
return ((IMAPStore) getStore()).hasCapability(capability);
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-27 13:52:50 +00:00
|
|
|
public boolean isOpen() {
|
|
|
|
return (iservice != null && iservice.isConnected());
|
|
|
|
}
|
|
|
|
|
2019-07-29 09:17:12 +00:00
|
|
|
public void close() throws MessagingException {
|
|
|
|
try {
|
2019-10-08 19:59:49 +00:00
|
|
|
if (iservice != null && iservice.isConnected())
|
2019-07-29 09:17:12 +00:00
|
|
|
iservice.close();
|
|
|
|
} finally {
|
2019-07-30 16:53:18 +00:00
|
|
|
context = null;
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2020-08-23 20:50:26 +00:00
|
|
|
private static class SocketFactoryService extends SocketFactory {
|
|
|
|
private SocketFactory factory = SocketFactory.getDefault();
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket() throws IOException {
|
|
|
|
return configure(factory.createSocket());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
|
|
|
return configure(factory.createSocket(host, port));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(InetAddress host, int port) throws IOException {
|
|
|
|
return configure(factory.createSocket(host, port));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
|
|
|
|
return configure(factory.createSocket(host, port, localHost, localPort));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
|
|
|
return configure(factory.createSocket(address, port, localAddress, localPort));
|
|
|
|
}
|
|
|
|
|
2020-08-27 08:15:26 +00:00
|
|
|
private Socket configure(Socket socket) throws SocketException {
|
|
|
|
configureSocketOptions(socket);
|
2020-08-23 20:50:26 +00:00
|
|
|
return socket;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-18 13:20:16 +00:00
|
|
|
private static class SSLSocketFactoryService extends SSLSocketFactory {
|
2019-12-17 15:11:28 +00:00
|
|
|
// openssl s_client -connect host:port < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin
|
2019-12-18 15:36:54 +00:00
|
|
|
private String server;
|
|
|
|
private boolean secure;
|
2020-01-29 11:44:55 +00:00
|
|
|
private boolean harden;
|
2019-12-18 15:36:54 +00:00
|
|
|
private String trustedFingerprint;
|
|
|
|
private SSLSocketFactory factory;
|
2019-12-17 15:11:28 +00:00
|
|
|
private X509Certificate certificate;
|
2019-12-17 09:58:42 +00:00
|
|
|
|
2020-02-20 14:35:10 +00:00
|
|
|
SSLSocketFactoryService(String host, boolean insecure, boolean harden, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException {
|
2019-12-18 15:36:54 +00:00
|
|
|
this.server = host;
|
|
|
|
this.secure = !insecure;
|
2020-01-29 11:44:55 +00:00
|
|
|
this.harden = harden;
|
2019-12-18 15:36:54 +00:00
|
|
|
this.trustedFingerprint = fingerprint;
|
|
|
|
|
|
|
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
2019-12-18 18:25:56 +00:00
|
|
|
tmf.init((KeyStore) null);
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
TrustManager[] tms = tmf.getTrustManagers();
|
|
|
|
if (tms == null || tms.length == 0 || !(tms[0] instanceof X509TrustManager)) {
|
|
|
|
Log.e("Missing root trust manager");
|
|
|
|
sslContext.init(null, tms, null);
|
|
|
|
} else {
|
|
|
|
final X509TrustManager rtm = (X509TrustManager) tms[0];
|
|
|
|
|
|
|
|
X509TrustManager tm = new X509TrustManager() {
|
2020-01-31 19:20:44 +00:00
|
|
|
// openssl s_client -connect <host>
|
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
@Override
|
|
|
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
|
|
|
if (secure)
|
|
|
|
rtm.checkClientTrusted(chain, authType);
|
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
@Override
|
|
|
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
|
|
|
certificate = chain[0];
|
|
|
|
|
|
|
|
if (secure) {
|
|
|
|
// Check if selected fingerprint
|
2020-01-31 19:20:44 +00:00
|
|
|
if (trustedFingerprint != null && matches(certificate, trustedFingerprint)) {
|
2019-12-18 15:36:54 +00:00
|
|
|
Log.i("Trusted selected fingerprint");
|
|
|
|
return;
|
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
// Check certificates
|
2019-12-19 16:15:29 +00:00
|
|
|
try {
|
|
|
|
rtm.checkServerTrusted(chain, authType);
|
|
|
|
} catch (CertificateException ex) {
|
|
|
|
Principal principal = certificate.getSubjectDN();
|
|
|
|
if (principal == null)
|
|
|
|
throw ex;
|
|
|
|
else
|
|
|
|
throw new CertificateException(principal.getName(), ex);
|
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
// Check host name
|
|
|
|
List<String> names = getDnsNames(certificate);
|
|
|
|
for (String name : names)
|
|
|
|
if (matches(server, name)) {
|
|
|
|
Log.i("Trusted server=" + server + " name=" + name);
|
|
|
|
return;
|
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
String error = server + " not in certificate: " + TextUtils.join(",", names);
|
2020-02-04 08:42:06 +00:00
|
|
|
Log.i(error);
|
2019-12-18 15:36:54 +00:00
|
|
|
throw new CertificateException(error);
|
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
@Override
|
|
|
|
public X509Certificate[] getAcceptedIssuers() {
|
|
|
|
return rtm.getAcceptedIssuers();
|
2019-12-18 13:20:16 +00:00
|
|
|
}
|
2019-12-18 15:36:54 +00:00
|
|
|
};
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2020-02-09 12:58:16 +00:00
|
|
|
KeyManager[] km = null;
|
2020-02-20 14:35:10 +00:00
|
|
|
if (key != null && chain != null)
|
2020-02-09 12:58:16 +00:00
|
|
|
try {
|
2020-02-20 14:35:10 +00:00
|
|
|
Log.i("Client certificate init");
|
2020-02-10 08:16:19 +00:00
|
|
|
|
2020-02-20 14:35:10 +00:00
|
|
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
|
|
|
ks.load(null, new char[0]);
|
|
|
|
ks.setKeyEntry(server, key, new char[0], chain);
|
2020-02-09 12:58:16 +00:00
|
|
|
|
2020-02-09 18:49:33 +00:00
|
|
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
2020-02-20 14:35:10 +00:00
|
|
|
kmf.init(ks, new char[0]);
|
2020-02-09 12:58:16 +00:00
|
|
|
km = kmf.getKeyManagers();
|
2020-02-10 08:16:19 +00:00
|
|
|
|
|
|
|
Log.i("Client certificate initialized");
|
2020-02-09 12:58:16 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
sslContext.init(km, new TrustManager[]{tm}, null);
|
2019-12-18 15:36:54 +00:00
|
|
|
}
|
2019-12-18 13:20:16 +00:00
|
|
|
|
2019-12-18 15:36:54 +00:00
|
|
|
factory = sslContext.getSocketFactory();
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
@Override
|
|
|
|
public Socket createSocket() throws IOException {
|
2019-12-18 15:36:54 +00:00
|
|
|
Log.e("createSocket");
|
|
|
|
throw new IOException("createSocket");
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
@Override
|
|
|
|
public Socket createSocket(String host, int port) throws IOException {
|
2020-01-28 15:32:26 +00:00
|
|
|
return configure(factory.createSocket(server, port));
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
@Override
|
|
|
|
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
2020-01-28 15:32:26 +00:00
|
|
|
return configure(factory.createSocket(s, server, port, autoClose));
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
@Override
|
|
|
|
public Socket createSocket(InetAddress address, int port) throws IOException {
|
2019-12-18 15:36:54 +00:00
|
|
|
Log.e("createSocket(address, port)");
|
|
|
|
throw new IOException("createSocket");
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(String host, int port, InetAddress clientAddress, int clientPort) throws IOException {
|
2020-01-28 15:32:26 +00:00
|
|
|
return configure(factory.createSocket(server, port, clientAddress, clientPort));
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Socket createSocket(InetAddress address, int port, InetAddress clientAddress, int clientPort) throws IOException {
|
2019-12-18 15:36:54 +00:00
|
|
|
Log.e("createSocket(address, port, clientAddress, clientPort)");
|
|
|
|
throw new IOException("createSocket");
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 08:15:26 +00:00
|
|
|
private Socket configure(Socket socket) throws SocketException {
|
|
|
|
configureSocketOptions(socket);
|
|
|
|
|
2020-05-24 15:36:26 +00:00
|
|
|
if (socket instanceof SSLSocket) {
|
2020-01-29 11:44:55 +00:00
|
|
|
SSLSocket sslSocket = (SSLSocket) socket;
|
|
|
|
|
2020-05-24 16:59:00 +00:00
|
|
|
if (!secure) {
|
|
|
|
sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
|
|
|
|
|
|
|
|
List<String> ciphers = new ArrayList<>();
|
|
|
|
for (String cipher : sslSocket.getSupportedCipherSuites())
|
|
|
|
if (!cipher.endsWith("_SCSV"))
|
|
|
|
ciphers.add(cipher);
|
|
|
|
sslSocket.setEnabledCipherSuites(ciphers.toArray(new String[0]));
|
|
|
|
} else if (harden) {
|
|
|
|
List<String> protocols = new ArrayList<>();
|
|
|
|
for (String protocol : sslSocket.getEnabledProtocols())
|
|
|
|
if (SSL_PROTOCOL_BLACKLIST.contains(protocol))
|
|
|
|
Log.i("SSL disabling protocol=" + protocol);
|
|
|
|
else
|
|
|
|
protocols.add(protocol);
|
|
|
|
sslSocket.setEnabledProtocols(protocols.toArray(new String[0]));
|
|
|
|
|
|
|
|
List<String> ciphers = new ArrayList<>();
|
|
|
|
for (String cipher : sslSocket.getEnabledCipherSuites()) {
|
|
|
|
if (SSL_CIPHER_BLACKLIST.matcher(cipher).matches())
|
|
|
|
Log.i("SSL disabling cipher=" + cipher);
|
|
|
|
else
|
|
|
|
ciphers.add(cipher);
|
|
|
|
}
|
|
|
|
sslSocket.setEnabledCipherSuites(ciphers.toArray(new String[0]));
|
2020-05-24 17:11:39 +00:00
|
|
|
} else {
|
|
|
|
List<String> ciphers = new ArrayList<>();
|
|
|
|
ciphers.addAll(Arrays.asList(sslSocket.getEnabledCipherSuites()));
|
|
|
|
for (String cipher : sslSocket.getSupportedCipherSuites())
|
|
|
|
if (cipher.contains("3DES")) {
|
|
|
|
// Some servers support 3DES and RC4 only
|
|
|
|
Log.i("SSL enabling cipher=" + cipher);
|
|
|
|
ciphers.add(cipher);
|
|
|
|
}
|
|
|
|
sslSocket.setEnabledCipherSuites(ciphers.toArray(new String[0]));
|
2020-01-29 11:44:55 +00:00
|
|
|
}
|
2020-05-24 16:59:00 +00:00
|
|
|
|
|
|
|
Log.i("SSL protocols=" + TextUtils.join(",", sslSocket.getEnabledProtocols()));
|
|
|
|
Log.i("SSL ciphers=" + TextUtils.join(",", sslSocket.getEnabledCipherSuites()));
|
2020-01-28 15:32:26 +00:00
|
|
|
}
|
2020-01-29 11:44:55 +00:00
|
|
|
|
2020-01-28 15:32:26 +00:00
|
|
|
return socket;
|
|
|
|
}
|
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
@Override
|
|
|
|
public String[] getDefaultCipherSuites() {
|
2019-12-18 15:36:54 +00:00
|
|
|
return factory.getDefaultCipherSuites();
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String[] getSupportedCipherSuites() {
|
2019-12-18 15:36:54 +00:00
|
|
|
return factory.getSupportedCipherSuites();
|
2019-12-17 15:11:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean matches(String server, String name) {
|
|
|
|
if (name.startsWith("*.")) {
|
|
|
|
// Wildcard certificate
|
|
|
|
String domain = name.substring(2);
|
|
|
|
if (TextUtils.isEmpty(domain))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
int dot = server.indexOf(".");
|
|
|
|
if (dot < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
String cdomain = server.substring(dot + 1);
|
|
|
|
if (TextUtils.isEmpty(cdomain))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return domain.equalsIgnoreCase(cdomain);
|
2019-12-18 13:20:16 +00:00
|
|
|
} else
|
2019-12-17 15:11:28 +00:00
|
|
|
return server.equalsIgnoreCase(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static List<String> getDnsNames(X509Certificate certificate) throws CertificateParsingException {
|
|
|
|
List<String> result = new ArrayList<>();
|
|
|
|
|
|
|
|
Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
|
|
|
|
if (altNames == null)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
for (List altName : altNames)
|
|
|
|
if (altName.get(0).equals(GeneralName.dNSName))
|
|
|
|
result.add((String) altName.get(1));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-01-31 19:20:44 +00:00
|
|
|
private static boolean matches(X509Certificate certificate, @NonNull String trustedFingerprint) {
|
|
|
|
// Get certificate fingerprint
|
|
|
|
try {
|
|
|
|
String fingerprint = getFingerPrint(certificate);
|
|
|
|
int slash = trustedFingerprint.indexOf('/');
|
|
|
|
if (slash < 0)
|
|
|
|
return trustedFingerprint.equals(fingerprint);
|
|
|
|
else {
|
|
|
|
String keyId = getKeyId(certificate);
|
|
|
|
if (trustedFingerprint.substring(slash + 1).equals(keyId))
|
|
|
|
return true;
|
|
|
|
return trustedFingerprint.substring(0, slash).equals(fingerprint);
|
|
|
|
}
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getKeyId(X509Certificate certificate) {
|
|
|
|
try {
|
|
|
|
byte[] extension = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId());
|
|
|
|
if (extension == null)
|
|
|
|
return null;
|
|
|
|
byte[] bytes = DEROctetString.getInstance(extension).getOctets();
|
|
|
|
SubjectKeyIdentifier keyId = SubjectKeyIdentifier.getInstance(bytes);
|
|
|
|
return Helper.hex(keyId.getKeyIdentifier());
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 15:11:28 +00:00
|
|
|
private static String getFingerPrint(X509Certificate certificate) throws CertificateEncodingException, NoSuchAlgorithmException {
|
|
|
|
return Helper.sha1(certificate.getEncoded());
|
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
|
2020-01-31 19:20:44 +00:00
|
|
|
String getFingerPrintSelect() {
|
2019-12-18 13:20:16 +00:00
|
|
|
try {
|
2020-07-10 12:33:08 +00:00
|
|
|
if (certificate == null)
|
|
|
|
return null;
|
2020-01-31 19:20:44 +00:00
|
|
|
String keyId = getKeyId(certificate);
|
|
|
|
String fingerPrint = getFingerPrint(certificate);
|
|
|
|
return fingerPrint + (keyId == null ? "" : "/" + keyId);
|
2019-12-18 13:20:16 +00:00
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
return null;
|
|
|
|
}
|
2019-12-16 15:32:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-27 08:15:26 +00:00
|
|
|
private static void configureSocketOptions(Socket socket) throws SocketException {
|
2020-08-27 12:46:04 +00:00
|
|
|
int timeout = socket.getSoTimeout();
|
|
|
|
boolean keepAlive = socket.getKeepAlive();
|
|
|
|
int linger = socket.getSoLinger();
|
|
|
|
|
2020-08-27 08:15:26 +00:00
|
|
|
Log.i("Socket type=" + socket.getClass() +
|
2020-08-27 12:46:04 +00:00
|
|
|
" timeout=" + timeout +
|
|
|
|
" keep-alive=" + keepAlive +
|
|
|
|
" linger=" + linger);
|
2020-08-27 08:15:26 +00:00
|
|
|
|
2020-08-27 12:46:04 +00:00
|
|
|
if (keepAlive) {
|
|
|
|
Log.e("Socket keep-alive=" + keepAlive);
|
2020-08-27 08:15:26 +00:00
|
|
|
socket.setKeepAlive(false);
|
|
|
|
}
|
2020-08-27 12:46:04 +00:00
|
|
|
|
|
|
|
if (linger >= 0) {
|
|
|
|
Log.e("Socket linger=" + linger);
|
|
|
|
socket.setSoLinger(false, -1);
|
|
|
|
}
|
2020-08-27 08:15:26 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 15:32:25 +00:00
|
|
|
class UntrustedException extends MessagingException {
|
|
|
|
private String fingerprint;
|
|
|
|
|
2019-12-16 18:09:49 +00:00
|
|
|
UntrustedException(@NonNull String fingerprint, @NonNull Exception cause) {
|
2019-12-18 13:20:16 +00:00
|
|
|
super("Untrusted", cause);
|
2019-12-16 15:32:25 +00:00
|
|
|
this.fingerprint = fingerprint;
|
|
|
|
}
|
|
|
|
|
|
|
|
String getFingerprint() {
|
|
|
|
return fingerprint;
|
|
|
|
}
|
|
|
|
|
2020-09-10 09:14:51 +00:00
|
|
|
@NonNull
|
2019-12-16 15:32:25 +00:00
|
|
|
@Override
|
|
|
|
public synchronized String toString() {
|
2019-12-16 18:09:49 +00:00
|
|
|
return getCause().toString();
|
2019-12-16 15:32:25 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-29 09:17:12 +00:00
|
|
|
}
|