diff --git a/app/src/main/java/eu/faircode/email/ConnectionHelper.java b/app/src/main/java/eu/faircode/email/ConnectionHelper.java index 8e01575e0a..da0733ba8b 100644 --- a/app/src/main/java/eu/faircode/email/ConnectionHelper.java +++ b/app/src/main/java/eu/faircode/email/ConnectionHelper.java @@ -30,6 +30,7 @@ import android.net.NetworkInfo; import android.os.Build; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; @@ -632,6 +633,45 @@ public class ConnectionHelper { } } + private static boolean isCarrierGradeNat(InetAddress addr) { + if (!(addr instanceof Inet4Address)) + return false; + byte[] b = addr.getAddress(); + return (b[0] & 0xFF) == 100 && (b[1] & 0xC0) == 0x40; // 100.64.0.0/10 + } + + private static boolean isUniqueLocalAddress(InetAddress addr) { + if (!(addr instanceof Inet6Address)) + return false; + byte[] b = addr.getAddress(); + return (b[0] & 0xFE) == 0xFC; // fc00::/7 + } + + static boolean isLocalAddress(String host, boolean unwrap) { + if (TextUtils.isEmpty(host)) + return false; + try { + InetAddress addr = InetAddress.getByName(host); + if (unwrap) + addr = unwrapEmbeddedV4(addr); + return (addr.isLoopbackAddress() || + addr.isSiteLocalAddress() || + addr.isLinkLocalAddress() || + isCarrierGradeNat(addr) || + isUniqueLocalAddress(addr)); + } catch (Throwable ex) { + Log.e(ex); + return false; + } + } + + static boolean isNumericAddress(String host) { + // IPv4-mapped IPv6 can be 45 characters + if (host == null || host.length() > 64) + return false; + return ConnectionHelper.jni_is_numeric_address(host); + } + static InetAddress from6to4(InetAddress addr) { // https://en.wikipedia.org/wiki/6to4 if (addr instanceof Inet6Address) { @@ -646,23 +686,46 @@ public class ConnectionHelper { return addr; } - static boolean isNumericAddress(String host) { - // IPv4-mapped IPv6 can be 45 characters - if (host == null || host.length() > 64) - return false; - return ConnectionHelper.jni_is_numeric_address(host); + private static final byte[] MAPPED_V4_PREFIX = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF + }; + + static InetAddress fromMappedV4(InetAddress addr) { + if (addr instanceof Inet6Address) { + byte[] octets = ((Inet6Address) addr).getAddress(); + if (Arrays.equals(Arrays.copyOfRange(octets, 0, 12), MAPPED_V4_PREFIX)) + try { + return Inet4Address.getByAddress(Arrays.copyOfRange(octets, 12, 16)); + } catch (Throwable ex) { + Log.e(ex); + } + } + return addr; } - static boolean isLocalAddress(String host) { - try { - InetAddress addr = ConnectionHelper.from6to4(InetAddress.getByName(host)); - return (addr.isLoopbackAddress() || - addr.isSiteLocalAddress() || - addr.isLinkLocalAddress()); - } catch (UnknownHostException ex) { - Log.e(ex); - return false; + private static final byte[] NAT64_PREFIX = { + 0x00, 0x64, (byte) 0xFF, (byte) 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + static InetAddress fromNat64(InetAddress addr) { + if (addr instanceof Inet6Address) { + byte[] octets = ((Inet6Address) addr).getAddress(); + if (Arrays.equals(Arrays.copyOfRange(octets, 0, 12), NAT64_PREFIX)) + try { + return Inet4Address.getByAddress(Arrays.copyOfRange(octets, 12, 16)); + } catch (Throwable ex) { + Log.e(ex); + } } + return addr; + } + + static InetAddress unwrapEmbeddedV4(InetAddress addr) { + addr = from6to4(addr); + if (addr instanceof Inet4Address) return addr; + addr = fromMappedV4(addr); + if (addr instanceof Inet4Address) return addr; + return fromNat64(addr); } static boolean inSubnet(final String ip, final String net, final int prefix) { diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index 8aa3fc5906..f565aac071 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -2170,45 +2170,56 @@ public class FragmentAccount extends FragmentBase { private Snackbar lanSnackbar = null; private void checkLan(String host) { - boolean lan = false; - if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) - try { - Context context = getContext(); - InetAddress addr = InetAddress.getByName(host); - lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) && - !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK); - } catch (Throwable ignored) { - } - - if (lan && lanSnackbar == null) { - lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); - Helper.setSnackbarLines(lanSnackbar, 2); - lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { - @Override - public void onClick(View v) { + Bundle args = new Bundle(); + args.putString("host", host); + + new SimpleTask() { + @Override + protected Boolean onExecute(Context context, Bundle args) throws Throwable { + String host = args.getString("host"); + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN && + ConnectionHelper.isLocalAddress(host, false) && + !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK)); + } + + @Override + protected void onExecuted(Bundle args, Boolean data) { + boolean lan17 = Boolean.TRUE.equals(data); + if (lan17 && lanSnackbar == null) { + lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); + Helper.setSnackbarLines(lanSnackbar, 2); + lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { + @Override + public void onClick(View v) { + lanSnackbar.dismiss(); + if (BuildConfig.PLAY_STORE_RELEASE) + Helper.viewFAQ(v.getContext(), 210); + else + requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); + } + }); + lanSnackbar.addCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + view.requestApplyInsets(); + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + view.requestApplyInsets(); + } + }); + lanSnackbar.show(); + } else if (!lan17 && lanSnackbar != null) { lanSnackbar.dismiss(); - if (BuildConfig.PLAY_STORE_RELEASE) - Helper.viewFAQ(v.getContext(), 210); - else - requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); - } - }); - lanSnackbar.addCallback(new Snackbar.Callback() { - @Override - public void onShown(Snackbar sb) { - view.requestApplyInsets(); + lanSnackbar = null; } + } - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - view.requestApplyInsets(); - } - }); - lanSnackbar.show(); - } else if (!lan && lanSnackbar != null) { - lanSnackbar.dismiss(); - lanSnackbar = null; - } + @Override + protected void onException(Bundle args, Throwable ignored) { + } + }.execute(this, args, "checklan"); } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 8bae455e5a..58b8247b2e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -1718,45 +1718,56 @@ public class FragmentIdentity extends FragmentBase { private Snackbar lanSnackbar = null; private void checkLan(String host) { - boolean lan = false; - if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) - try { - Context context = getContext(); - InetAddress addr = InetAddress.getByName(host); - lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) && - !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK); - } catch (Throwable ignored) { + Bundle args = new Bundle(); + args.putString("host", host); + + new SimpleTask() { + @Override + protected Boolean onExecute(Context context, Bundle args) throws Throwable { + String host = args.getString("host"); + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN && + ConnectionHelper.isLocalAddress(host, false) && + !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK)); } - if (lan && lanSnackbar == null) { - lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); - Helper.setSnackbarLines(lanSnackbar, 2); - lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { - @Override - public void onClick(View v) { + @Override + protected void onExecuted(Bundle args, Boolean data) { + boolean lan17 = Boolean.TRUE.equals(data); + if (lan17 && lanSnackbar == null) { + lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); + Helper.setSnackbarLines(lanSnackbar, 2); + lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { + @Override + public void onClick(View v) { + lanSnackbar.dismiss(); + if (BuildConfig.PLAY_STORE_RELEASE) + Helper.viewFAQ(v.getContext(), 210); + else + requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); + } + }); + lanSnackbar.addCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + view.requestApplyInsets(); + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + view.requestApplyInsets(); + } + }); + lanSnackbar.show(); + } else if (!lan17 && lanSnackbar != null) { lanSnackbar.dismiss(); - if (BuildConfig.PLAY_STORE_RELEASE) - Helper.viewFAQ(v.getContext(), 210); - else - requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); - } - }); - lanSnackbar.addCallback(new Snackbar.Callback() { - @Override - public void onShown(Snackbar sb) { - view.requestApplyInsets(); + lanSnackbar = null; } + } - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - view.requestApplyInsets(); - } - }); - lanSnackbar.show(); - } else if (!lan && lanSnackbar != null) { - lanSnackbar.dismiss(); - lanSnackbar = null; - } + @Override + protected void onException(Bundle args, Throwable ignored) { + } + }.execute(this, args, "checklan"); } private void onPickUri(Intent intent) { diff --git a/app/src/main/java/eu/faircode/email/FragmentPop.java b/app/src/main/java/eu/faircode/email/FragmentPop.java index 54bff2c2c3..2ecb04ed81 100644 --- a/app/src/main/java/eu/faircode/email/FragmentPop.java +++ b/app/src/main/java/eu/faircode/email/FragmentPop.java @@ -1234,45 +1234,56 @@ public class FragmentPop extends FragmentBase { private Snackbar lanSnackbar = null; private void checkLan(String host) { - boolean lan = false; - if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) - try { - Context context = getContext(); - InetAddress addr = InetAddress.getByName(host); - lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) && - !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK); - } catch (Throwable ignored) { + Bundle args = new Bundle(); + args.putString("host", host); + + new SimpleTask() { + @Override + protected Boolean onExecute(Context context, Bundle args) throws Throwable { + String host = args.getString("host"); + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN && + ConnectionHelper.isLocalAddress(host, false) && + !Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK)); } - if (lan && lanSnackbar == null) { - lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); - Helper.setSnackbarLines(lanSnackbar, 2); - lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { - @Override - public void onClick(View v) { + @Override + protected void onExecuted(Bundle args, Boolean data) { + boolean lan17 = Boolean.TRUE.equals(data); + if (lan17 && lanSnackbar == null) { + lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE)); + Helper.setSnackbarLines(lanSnackbar, 2); + lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { + @Override + public void onClick(View v) { + lanSnackbar.dismiss(); + if (BuildConfig.PLAY_STORE_RELEASE) + Helper.viewFAQ(v.getContext(), 210); + else + requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); + } + }); + lanSnackbar.addCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + view.requestApplyInsets(); + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + view.requestApplyInsets(); + } + }); + lanSnackbar.show(); + } else if (!lan17 && lanSnackbar != null) { lanSnackbar.dismiss(); - if (BuildConfig.PLAY_STORE_RELEASE) - Helper.viewFAQ(v.getContext(), 210); - else - requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS); - } - }); - lanSnackbar.addCallback(new Snackbar.Callback() { - @Override - public void onShown(Snackbar sb) { - view.requestApplyInsets(); + lanSnackbar = null; } + } - @Override - public void onDismissed(Snackbar transientBottomBar, int event) { - view.requestApplyInsets(); - } - }); - lanSnackbar.show(); - } else if (!lan && lanSnackbar != null) { - lanSnackbar.dismiss(); - lanSnackbar = null; - } + @Override + protected void onException(Bundle args, Throwable ignored) { + } + }.execute(this, args, "checklan"); } private List getSwipeActions(Context context) { diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index d5040ad3c2..b9bcc18a6e 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -3391,14 +3391,14 @@ public class MessageHelper { if (ip.toLowerCase(Locale.ROOT).startsWith("ipv6:")) ip = ip.substring(5); if (ConnectionHelper.isNumericAddress(ip) && - ConnectionHelper.isLocalAddress(ip)) + ConnectionHelper.isLocalAddress(ip, true)) return true; } int f = value.indexOf(' '); String host = (f < 0 ? value : value.substring(0, f)); if (ConnectionHelper.isNumericAddress(host)) { - if (ConnectionHelper.isLocalAddress(host)) + if (ConnectionHelper.isLocalAddress(host, true)) return true; }