diff --git a/README.md b/README.md index ffd089bda7..59c2847322 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ FairEmail uses: * [OpenPGP API library](https://github.com/open-keychain/openpgp-api). Copyright (C) 2014-2015 Dominik Schürmann. [Apache License 2.0](https://github.com/open-keychain/openpgp-api/blob/master/LICENSE). * [Android SQLite support library](https://github.com/requery/sqlite-android). Copyright (C) 2017 requery.io. [Apache License 2.0](https://github.com/requery/sqlite-android/blob/master/LICENSE). * [App shortcut icon generator](https://romannurik.github.io/AndroidAssetStudio/icons-app-shortcut.html). Copyright ???. [Apache License 2.0](https://github.com/romannurik/AndroidAssetStudio/blob/master/LICENSE). +* [Mozilla ISPDB](https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration#ISPDB). *Free to use for any client.* ## License diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index 9a3eb0f9f2..9d257a3bf5 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -62,12 +62,7 @@ import com.google.android.material.textfield.TextInputLayout; import com.sun.mail.imap.IMAPFolder; import com.sun.mail.imap.IMAPStore; -import org.xbill.DNS.Lookup; -import org.xbill.DNS.Record; -import org.xbill.DNS.SRVRecord; -import org.xbill.DNS.SimpleResolver; -import org.xbill.DNS.Type; - +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -267,53 +262,40 @@ public class FragmentAccount extends FragmentEx { btnAutoConfig.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - etDomain.setEnabled(false); - btnAutoConfig.setEnabled(false); Bundle args = new Bundle(); args.putString("domain", etDomain.getText().toString()); - new SimpleTask() { + new SimpleTask() { @Override - protected SRVRecord onLoad(Context context, Bundle args) throws Throwable { - // https://tools.ietf.org/html/rfc6186 - String dns = "_imaps._tcp." + args.getString("domain"); - Lookup lookup = new Lookup(dns, Type.SRV); - // https://dns.watch/ - SimpleResolver resolver = new SimpleResolver("84.200.69.80"); - lookup.setResolver(resolver); - Log.i("Lookup dns=" + dns + " @" + resolver.getAddress()); - Record[] records = lookup.run(); - if (lookup.getResult() != Lookup.SUCCESSFUL) - throw new IllegalArgumentException(lookup.getErrorString()); - Log.i("Found dns=" + (records == null ? -1 : records.length)); - if (records != null) - for (int i = 0; i < records.length; i++) { - SRVRecord srv = (SRVRecord) records[i]; - Log.i("SRV=" + srv); - return srv; - } - - throw new IllegalArgumentException(getString(R.string.title_no_settings)); + protected void onInit(Bundle args) { + etDomain.setEnabled(false); + btnAutoConfig.setEnabled(false); } @Override - protected void onLoaded(Bundle args, SRVRecord srv) { + protected void onCleanup(Bundle args) { etDomain.setEnabled(true); btnAutoConfig.setEnabled(true); - if (srv != null) { - etHost.setText(srv.getTarget().toString(true)); - etPort.setText(Integer.toString(srv.getPort())); - cbStartTls.setChecked(srv.getPort() == 143); - } + } + + @Override + protected Provider onLoad(Context context, Bundle args) throws Throwable { + String domain = args.getString("domain"); + return Provider.fromDomain(context, domain); + } + + @Override + protected void onLoaded(Bundle args, Provider provider) { + etHost.setText(provider.imap_host); + etPort.setText(Integer.toString(provider.imap_port)); + cbStartTls.setChecked(provider.imap_starttls); } @Override protected void onException(Bundle args, Throwable ex) { - etDomain.setEnabled(true); - btnAutoConfig.setEnabled(true); - if (ex instanceof IllegalArgumentException) - Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + if (ex instanceof IOException) + Snackbar.make(view, R.string.title_no_settings, Snackbar.LENGTH_LONG).show(); else Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); } diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index bb4194d312..61ff8e5801 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -58,12 +58,7 @@ import com.android.colorpicker.ColorPickerSwatch; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputLayout; -import org.xbill.DNS.Lookup; -import org.xbill.DNS.Record; -import org.xbill.DNS.SRVRecord; -import org.xbill.DNS.SimpleResolver; -import org.xbill.DNS.Type; - +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -364,47 +359,36 @@ public class FragmentIdentity extends FragmentEx { Bundle args = new Bundle(); args.putString("domain", etDomain.getText().toString()); - new SimpleTask() { + new SimpleTask() { @Override - protected SRVRecord onLoad(Context context, Bundle args) throws Throwable { - // https://tools.ietf.org/html/rfc6186 - String dns = "_submission._tcp." + args.getString("domain"); - Lookup lookup = new Lookup(dns, Type.SRV); - // https://dns.watch/ - SimpleResolver resolver = new SimpleResolver("84.200.69.80"); - lookup.setResolver(resolver); - Log.i("Lookup dns=" + dns + " @" + resolver.getAddress()); - Record[] records = lookup.run(); - if (lookup.getResult() != Lookup.SUCCESSFUL) - throw new IllegalArgumentException(lookup.getErrorString()); - Log.i("Found dns=" + (records == null ? -1 : records.length)); - if (records != null) - for (int i = 0; i < records.length; i++) { - SRVRecord srv = (SRVRecord) records[i]; - Log.i("SRV=" + srv); - return srv; - } - - throw new IllegalArgumentException(getString(R.string.title_no_settings)); + protected void onInit(Bundle args) { + etDomain.setEnabled(false); + btnAutoConfig.setEnabled(false); } @Override - protected void onLoaded(Bundle args, SRVRecord srv) { + protected void onCleanup(Bundle args) { etDomain.setEnabled(true); btnAutoConfig.setEnabled(true); - if (srv != null) { - etHost.setText(srv.getTarget().toString(true)); - etPort.setText(Integer.toString(srv.getPort())); - cbStartTls.setChecked(srv.getPort() == 587); - } + } + + @Override + protected Provider onLoad(Context context, Bundle args) throws Throwable { + String domain = args.getString("domain"); + return Provider.fromDomain(context, domain); + } + + @Override + protected void onLoaded(Bundle args, Provider provider) { + etHost.setText(provider.smtp_host); + etPort.setText(Integer.toString(provider.smtp_port)); + cbStartTls.setChecked(provider.smtp_starttls); } @Override protected void onException(Bundle args, Throwable ex) { - etDomain.setEnabled(true); - btnAutoConfig.setEnabled(true); - if (ex instanceof IllegalArgumentException) - Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + if (ex instanceof IOException) + Snackbar.make(view, R.string.title_no_settings, Snackbar.LENGTH_LONG).show(); else Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); } diff --git a/app/src/main/java/eu/faircode/email/Provider.java b/app/src/main/java/eu/faircode/email/Provider.java index 8743f767fe..0a9222a95a 100644 --- a/app/src/main/java/eu/faircode/email/Provider.java +++ b/app/src/main/java/eu/faircode/email/Provider.java @@ -22,8 +22,21 @@ package eu.faircode.email; import android.content.Context; import android.content.res.XmlResourceParser; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.SimpleResolver; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.UnknownHostException; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -54,9 +67,9 @@ public class Provider { static List loadProfiles(Context context) { List result = null; try { + Provider provider = null; XmlResourceParser xml = context.getResources().getXml(R.xml.providers); int eventType = xml.getEventType(); - Provider provider = null; while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { if ("providers".equals(xml.getName())) @@ -107,6 +120,145 @@ public class Provider { return result; } + static Provider fromDomain(Context context, String domain) throws IOException, XmlPullParserException { + try { + return Provider.fromDNS(context, domain); + } catch (TextParseException ex) { + Log.w(ex); + throw new UnknownHostException(domain); + } catch (UnknownHostException ex) { + Log.w(ex); + return Provider.fromConfig(context, domain); + } + } + + private static Provider fromConfig(Context context, String domain) throws IOException, XmlPullParserException { + // https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat + URL url = new URL("https://autoconfig.thunderbird.net/v1.1/" + domain); + Log.i("Fetching " + url); + + HttpURLConnection request = (HttpURLConnection) url.openConnection(); + request.setReadTimeout(20 * 1000); + request.setConnectTimeout(20 * 1000); + request.setRequestMethod("GET"); + request.setDoInput(true); + request.connect(); + + // https://developer.android.com/reference/org/xmlpull/v1/XmlPullParser + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser xml = factory.newPullParser(); + xml.setInput(new InputStreamReader(request.getInputStream())); + + boolean imap = false; + boolean smtp = false; + Provider provider = new Provider(domain); + int eventType = xml.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if ("incomingServer".equals(xml.getName())) + imap = "imap".equals(xml.getAttributeValue(null, "type")); + + else if ("outgoingServer".equals(xml.getName())) + smtp = "smtp".equals(xml.getAttributeValue(null, "type")); + + else if ("hostname".equals(xml.getName())) { + eventType = xml.next(); + if (eventType == XmlPullParser.TEXT) { + String host = xml.getText(); + Log.i("Host=" + host); + if (imap) + provider.imap_host = host; + else if (smtp) + provider.smtp_host = host; + } + continue; + + } else if ("port".equals(xml.getName())) { + eventType = xml.next(); + if (eventType == XmlPullParser.TEXT) { + String port = xml.getText(); + Log.i("Port=" + port); + if (imap) { + provider.imap_port = Integer.parseInt(port); + provider.imap_starttls = (provider.imap_port == 143); + } else if (smtp) { + provider.smtp_port = Integer.parseInt(port); + provider.smtp_starttls = (provider.smtp_port == 587); + } + } + continue; + + } else if ("socketType".equals(xml.getName())) { + eventType = xml.next(); + if (eventType == XmlPullParser.TEXT) { + String socket = xml.getText(); + Log.i("Socket=" + socket); + if ("SSL".equals(socket)) { + if (imap) + provider.imap_starttls = false; + else if (smtp) + provider.smtp_starttls = false; + } else if ("STARTTLS".equals(socket)) { + if (imap) + provider.imap_starttls = true; + else if (smtp) + provider.smtp_starttls = true; + } + } + + continue; + } + } else if (eventType == XmlPullParser.END_TAG) { + if ("incomingServer".equals(xml.getName())) + imap = false; + else if ("outgoingServer".equals(xml.getName())) + smtp = false; + } + + eventType = xml.next(); + } + + request.disconnect(); + + Log.i("imap=" + provider.imap_host + ":" + provider.imap_port + ":" + provider.imap_starttls); + Log.i("smtp=" + provider.smtp_host + ":" + provider.smtp_port + ":" + provider.smtp_starttls); + return provider; + } + + private static Provider fromDNS(Context context, String domain) throws TextParseException, UnknownHostException { + // https://tools.ietf.org/html/rfc6186 + SRVRecord imap = lookup("_imaps._tcp." + domain); + SRVRecord smtp = lookup("_submission._tcp." + domain); + + Provider provider = new Provider(domain); + provider.imap_host = imap.getTarget().toString(true); + provider.imap_port = imap.getPort(); + provider.imap_starttls = (provider.imap_port == 143); + + provider.smtp_host = smtp.getTarget().toString(true); + provider.smtp_port = smtp.getPort(); + provider.smtp_starttls = (provider.smtp_port == 587); + + return provider; + } + + private static SRVRecord lookup(String dns) throws TextParseException, UnknownHostException { + Lookup lookup = new Lookup(dns, Type.SRV); + + // https://dns.watch/ + SimpleResolver resolver = new SimpleResolver("84.200.69.80"); + lookup.setResolver(resolver); + Log.i("Lookup dns=" + dns + " @" + resolver.getAddress()); + Record[] records = lookup.run(); + if (lookup.getResult() != Lookup.SUCCESSFUL) + if (lookup.getResult() == Lookup.HOST_NOT_FOUND) + throw new UnknownHostException(dns); + else + throw new IllegalArgumentException(lookup.getErrorString()); + Log.i("Found dns=" + (records == null ? -1 : records.length)); + return (records == null || records.length == 0 ? null : (SRVRecord) records[0]); + } + public int getAuthType() { if ("com.google".equals(type)) return Helper.AUTH_TYPE_GMAIL;