mirror of
https://github.com/M66B/FairEmail.git
synced 2026-03-27 03:15:39 +01:00
Added mini connection checker
This commit is contained in:
@@ -36,7 +36,9 @@ import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.sun.mail.iap.ConnectionException;
|
||||
import com.sun.mail.util.FolderClosedIOException;
|
||||
import com.sun.mail.util.LineInputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
@@ -46,6 +48,8 @@ import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -804,4 +808,102 @@ public class ConnectionHelper {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static SSLSocket starttls(Socket socket, String host, int port, Context context) throws IOException {
|
||||
String response;
|
||||
String command;
|
||||
boolean has = false;
|
||||
|
||||
LineInputStream lis =
|
||||
new LineInputStream(
|
||||
new BufferedInputStream(
|
||||
socket.getInputStream()));
|
||||
|
||||
if (port == 587) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null && !response.startsWith("220 "));
|
||||
|
||||
command = "EHLO " + EmailService.getDefaultEhlo() + "\n";
|
||||
EntityLog.log(context, socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null) {
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
if (response.contains("STARTTLS"))
|
||||
has = true;
|
||||
}
|
||||
} while (response != null &&
|
||||
response.length() >= 4 && response.charAt(3) == '-');
|
||||
|
||||
if (has) {
|
||||
command = "STARTTLS\n";
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
}
|
||||
} else if (port == 143) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null) {
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
if (response.contains("STARTTLS"))
|
||||
has = true;
|
||||
}
|
||||
} while (response != null &&
|
||||
!response.startsWith("* OK"));
|
||||
|
||||
if (has) {
|
||||
command = "A001 STARTTLS\n";
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
if (has) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null &&
|
||||
!(response.startsWith("A001 OK") || response.startsWith("220 ")));
|
||||
|
||||
SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
return (SSLSocket) sslFactory.createSocket(socket, host, port, false);
|
||||
} else
|
||||
throw new SocketException("No STARTTLS");
|
||||
}
|
||||
|
||||
static void signOff(Socket socket, int port, Context context) {
|
||||
try {
|
||||
String command = (port == 465 || port == 587 ? "QUIT" : "A002 LOGOUT");
|
||||
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write((command + "\n").getBytes());
|
||||
|
||||
LineInputStream lis =
|
||||
new LineInputStream(
|
||||
new BufferedInputStream(
|
||||
socket.getInputStream()));
|
||||
String response;
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null);
|
||||
} catch (IOException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,10 @@ import android.util.Xml;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.sun.mail.util.LineInputStream;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -50,7 +47,6 @@ import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.cert.Certificate;
|
||||
@@ -1360,7 +1356,7 @@ public class EmailProvider implements Parcelable {
|
||||
SSLSocket sslSocket = null;
|
||||
try {
|
||||
if (starttls)
|
||||
sslSocket = starttls(socket, context);
|
||||
sslSocket = ConnectionHelper.starttls(socket, host, port, context);
|
||||
else
|
||||
sslSocket = (SSLSocket) socket;
|
||||
|
||||
@@ -1405,7 +1401,7 @@ public class EmailProvider implements Parcelable {
|
||||
} finally {
|
||||
try {
|
||||
if (sslSocket != null) {
|
||||
signOff(sslSocket, context);
|
||||
ConnectionHelper.signOff(sslSocket, port, context);
|
||||
sslSocket.close();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
@@ -1440,104 +1436,6 @@ public class EmailProvider implements Parcelable {
|
||||
});
|
||||
}
|
||||
|
||||
private SSLSocket starttls(Socket socket, Context context) throws IOException {
|
||||
String response;
|
||||
String command;
|
||||
boolean has = false;
|
||||
|
||||
LineInputStream lis =
|
||||
new LineInputStream(
|
||||
new BufferedInputStream(
|
||||
socket.getInputStream()));
|
||||
|
||||
if (port == 587) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null && !response.startsWith("220 "));
|
||||
|
||||
command = "EHLO " + EmailService.getDefaultEhlo() + "\n";
|
||||
EntityLog.log(context, socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null) {
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
if (response.contains("STARTTLS"))
|
||||
has = true;
|
||||
}
|
||||
} while (response != null &&
|
||||
response.length() >= 4 && response.charAt(3) == '-');
|
||||
|
||||
if (has) {
|
||||
command = "STARTTLS\n";
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
}
|
||||
} else if (port == 143) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null) {
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
if (response.contains("STARTTLS"))
|
||||
has = true;
|
||||
}
|
||||
} while (response != null &&
|
||||
!response.startsWith("* OK"));
|
||||
|
||||
if (has) {
|
||||
command = "A001 STARTTLS\n";
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write(command.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
if (has) {
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null &&
|
||||
!(response.startsWith("A001 OK") || response.startsWith("220 ")));
|
||||
|
||||
SSLSocketFactory sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
return (SSLSocket) sslFactory.createSocket(socket, host, port, false);
|
||||
} else
|
||||
throw new SocketException("No STARTTLS");
|
||||
}
|
||||
|
||||
private void signOff(Socket socket, Context context) {
|
||||
try {
|
||||
String command = (port == 465 || port == 587 ? "QUIT" : "A002 LOGOUT");
|
||||
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " >" + command);
|
||||
socket.getOutputStream().write((command + "\n").getBytes());
|
||||
|
||||
LineInputStream lis =
|
||||
new LineInputStream(
|
||||
new BufferedInputStream(
|
||||
socket.getInputStream()));
|
||||
String response;
|
||||
do {
|
||||
response = lis.readLine();
|
||||
if (response != null)
|
||||
EntityLog.log(context, EntityLog.Type.Protocol,
|
||||
socket.getRemoteSocketAddress() + " <" + response);
|
||||
} while (response != null);
|
||||
} catch (IOException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Server) {
|
||||
|
||||
@@ -46,6 +46,7 @@ import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -58,6 +59,16 @@ import androidx.constraintlayout.widget.Group;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class FragmentOptionsConnection extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private View view;
|
||||
private ImageButton ibHelp;
|
||||
@@ -86,6 +97,10 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
|
||||
private TextView tvNetworkRoaming;
|
||||
private CardView cardDebug;
|
||||
private Button btnCiphers;
|
||||
private EditText etHost;
|
||||
private RadioGroup rgEncryption;
|
||||
private EditText etPort;
|
||||
private Button btnCheck;
|
||||
private TextView tvNetworkInfo;
|
||||
|
||||
private Group grpValidated;
|
||||
@@ -136,6 +151,10 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
|
||||
|
||||
cardDebug = view.findViewById(R.id.cardDebug);
|
||||
btnCiphers = view.findViewById(R.id.btnCiphers);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
rgEncryption = view.findViewById(R.id.rgEncryption);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
btnCheck = view.findViewById(R.id.btnCheck);
|
||||
tvNetworkInfo = view.findViewById(R.id.tvNetworkInfo);
|
||||
|
||||
grpValidated = view.findViewById(R.id.grpValidated);
|
||||
@@ -359,6 +378,121 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
|
||||
}
|
||||
});
|
||||
|
||||
btnCheck.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String host = etHost.getText().toString().trim();
|
||||
Integer port = Helper.parseInt(etPort.getText().toString().trim());
|
||||
|
||||
String encryption;
|
||||
if (rgEncryption.getCheckedRadioButtonId() == R.id.radio_starttls)
|
||||
encryption = "starttls";
|
||||
else if (rgEncryption.getCheckedRadioButtonId() == R.id.radio_ssl)
|
||||
encryption = "ssl";
|
||||
else
|
||||
encryption = "none";
|
||||
|
||||
int timeout = prefs.getInt("timeout", EmailService.DEFAULT_CONNECT_TIMEOUT) * 1000;
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString("host", host);
|
||||
args.putInt("port", port == null ? 0 : port);
|
||||
args.putString("encryption", encryption);
|
||||
args.putInt("timeout", timeout);
|
||||
|
||||
new SimpleTask<StringBuilder>() {
|
||||
@Override
|
||||
protected void onPreExecute(Bundle args) {
|
||||
btnCheck.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bundle args) {
|
||||
btnCheck.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringBuilder onExecute(Context context, Bundle args) throws Throwable {
|
||||
String host = args.getString("host");
|
||||
int port = args.getInt("port");
|
||||
String encryption = args.getString("encryption");
|
||||
int timeout = args.getInt("timeout");
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Host: ").append(host).append('\n');
|
||||
sb.append("Port: ").append(port).append('\n');
|
||||
sb.append("Encryption: ").append(encryption).append('\n');
|
||||
|
||||
InetSocketAddress address = new InetSocketAddress(host, port);
|
||||
SocketFactory factory = (!"ssl".equals(encryption)
|
||||
? SocketFactory.getDefault()
|
||||
: SSLSocketFactory.getDefault());
|
||||
try (Socket socket = factory.createSocket()) {
|
||||
socket.connect(address, timeout);
|
||||
socket.setSoTimeout(timeout);
|
||||
|
||||
if (!"none".equals(encryption)) {
|
||||
SSLSocket sslSocket = null;
|
||||
try {
|
||||
if ("starttls".equals(encryption))
|
||||
sslSocket = ConnectionHelper.starttls(socket, host, port, context);
|
||||
else
|
||||
sslSocket = (SSLSocket) socket;
|
||||
|
||||
sslSocket.startHandshake();
|
||||
|
||||
SSLSession session = sslSocket.getSession();
|
||||
sb.append("Protocol: ").append(session.getProtocol()).append('\n');
|
||||
sb.append("Cipher: ").append(session.getCipherSuite()).append('\n');
|
||||
Certificate[] certificates = session.getPeerCertificates();
|
||||
if (certificates != null)
|
||||
for (Certificate certificate : certificates) {
|
||||
if (certificate instanceof X509Certificate) {
|
||||
X509Certificate x = (X509Certificate) certificate;
|
||||
sb.append("Subject: ").append(x.getSubjectDN()).append('\n');
|
||||
for (String dns : EntityCertificate.getDnsNames(x))
|
||||
sb.append("DNS name: ").append(dns).append('\n');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (sslSocket != null) {
|
||||
ConnectionHelper.signOff(sslSocket, port, context);
|
||||
sslSocket.close();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, StringBuilder sb) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setIcon(R.drawable.twotone_info_24)
|
||||
.setTitle(R.string.title_advanced_section_connection)
|
||||
.setMessage(sb)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Do nothing
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Log.unexpectedError(getParentFragment(), ex);
|
||||
}
|
||||
}.execute(FragmentOptionsConnection.this, args, "connection:check");
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
FragmentDialogTheme.setBackground(getContext(), view, false);
|
||||
tvNetworkMetered.setVisibility(View.GONE);
|
||||
|
||||
@@ -611,6 +611,105 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvCaptionDebug" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHost"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_host"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnCiphers" />
|
||||
|
||||
<eu.faircode.email.EditTextPlain
|
||||
android:id="@+id/etHost"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="smtp.domain.tld"
|
||||
android:inputType="textUri"
|
||||
android:text="imap.gmail.com"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHost" />
|
||||
|
||||
<!-- SSL/STARTTLS -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvEncryption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_encryption"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etHost" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rgEncryption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvEncryption">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_ssl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/title_encryption_ssl" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_starttls"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_encryption_starttls" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_encryption_none"
|
||||
android:textColor="?attr/colorWarning"
|
||||
android:textStyle="bold" />
|
||||
</RadioGroup>
|
||||
|
||||
<!-- port -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPort"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_port"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/rgEncryption" />
|
||||
|
||||
<eu.faircode.email.EditTextPlain
|
||||
android:id="@+id/etPort"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:maxLength="5"
|
||||
android:text="465"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvPort" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCheck"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_check"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/etPort" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNetworkInfo"
|
||||
android:layout_width="0dp"
|
||||
@@ -624,7 +723,7 @@
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnCiphers" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnCheck" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
Reference in New Issue
Block a user