Added option to check sender's MX records

pull/159/head
M66B 5 years ago
parent 4fb3f3c638
commit b8203fe0f0

File diff suppressed because it is too large Load Diff

@ -649,7 +649,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
boolean authenticated =
!(Boolean.FALSE.equals(message.dkim) ||
Boolean.FALSE.equals(message.spf) ||
Boolean.FALSE.equals(message.dmarc));
Boolean.FALSE.equals(message.dmarc) ||
Boolean.FALSE.equals(message.mx));
// Line 3
ivType.setImageResource(message.drafts > 0
@ -3071,6 +3072,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
same = false;
Log.i("dmarc changed id=" + next.id);
}
if (!Objects.equals(prev.mx, next.mx)) {
same = false;
Log.i("mx changed id=" + next.id);
}
if (!Objects.equals(prev.avatar, next.avatar)) {
same = false;
Log.i("avatar changed id=" + next.id);

@ -17,7 +17,13 @@ import com.bugsnag.android.BreadcrumbType;
import com.bugsnag.android.Bugsnag;
import com.sun.mail.imap.IMAPStore;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -25,7 +31,10 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
public class ConnectionHelper {
// https://dns.watch/
@ -308,4 +317,26 @@ public class ConnectionHelper {
else
return dns.get(0).getHostAddress();
}
static void lookup(Address[] addresses, Context context) throws AddressException, TextParseException, UnknownHostException {
if (addresses != null)
for (Address address : addresses) {
String email = ((InternetAddress) address).getAddress();
if (email == null || !email.contains("@"))
throw new AddressException(email);
String domain = email.split("@")[1];
Lookup lookup = new Lookup(domain, Type.MX);
SimpleResolver resolver = new SimpleResolver(ConnectionHelper.getDnsServer(context));
lookup.setResolver(resolver);
Log.i("Lookup MX=" + domain + " @" + resolver.getAddress());
lookup.run();
if (lookup.getResult() == Lookup.HOST_NOT_FOUND ||
lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
Log.i("Lookup MX=" + domain + " result=" + lookup.getErrorString());
throw new IllegalArgumentException(context.getString(R.string.title_no_server, domain));
}
}
}
}

@ -1327,6 +1327,7 @@ class Core {
boolean process = false;
DB db = DB.getInstance(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
// Find message by uid (fast, no headers required)
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
@ -1429,6 +1430,18 @@ class Core {
Uri lookupUri = ContactInfo.getLookupUri(context, message.from);
message.avatar = (lookupUri == null ? null : lookupUri.toString());
boolean check_mx = prefs.getBoolean("check_mx", false);
if (check_mx)
try {
ConnectionHelper.lookup(
message.reply == null || message.reply.length == 0
? message.from : message.reply, context);
message.mx = true;
} catch (Throwable ex) {
message.warning = ex.getMessage();
message.mx = false;
}
/*
// Authentication is more reliable
Address sender = helper.getSender(); // header
@ -1480,7 +1493,6 @@ class Core {
if (state == null || state.networkState.isUnmetered())
maxSize = MessageHelper.SMALL_MESSAGE_SIZE;
else {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int downloadSize = prefs.getInt("download", 0);
maxSize = (downloadSize == 0
? MessageHelper.SMALL_MESSAGE_SIZE

@ -54,7 +54,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 92,
version = 93,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -904,6 +904,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("UPDATE `account` SET poll_interval = 24");
}
})
.addMigrations(new Migration(92, 93) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `message` ADD COLUMN `mx` INTEGER");
}
})
.build();
}

@ -99,6 +99,7 @@ public class EntityMessage implements Serializable {
public Boolean dkim;
public Boolean spf;
public Boolean dmarc;
public Boolean mx = null;
public String avatar; // lookup URI from sender
public String sender; // sort key
public Address[] from;
@ -251,6 +252,7 @@ public class EntityMessage implements Serializable {
Objects.equals(this.dkim, other.dkim) &&
Objects.equals(this.spf, other.spf) &&
Objects.equals(this.dmarc, other.dmarc) &&
Objects.equals(this.mx, other.mx) &&
Objects.equals(this.avatar, other.avatar) &&
Objects.equals(this.sender, other.sender) &&
MessageHelper.equal(this.from, other.from) &&

@ -113,10 +113,6 @@ import org.jsoup.nodes.Element;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
@ -128,7 +124,6 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@ -2412,6 +2407,7 @@ public class FragmentCompose extends FragmentBase {
EntityMessage draft;
DB db = DB.getInstance(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
try {
db.beginTransaction();
@ -2466,14 +2462,17 @@ public class FragmentCompose extends FragmentBase {
InternetAddress acc[] = null;
InternetAddress abcc[] = null;
boolean lookup_mx = prefs.getBoolean("lookup_mx", false);
if (!TextUtils.isEmpty(to))
try {
ato = InternetAddress.parse(to);
if (action == R.id.action_send)
for (InternetAddress address : ato) {
if (action == R.id.action_send) {
for (InternetAddress address : ato)
address.validate();
lookup(address, context);
}
if (lookup_mx)
ConnectionHelper.lookup(ato, context);
}
} catch (AddressException ex) {
throw new AddressException(context.getString(R.string.title_address_parse_error,
Helper.ellipsize(to, ADDRESS_ELLIPSIZE), ex.getMessage()));
@ -2483,10 +2482,10 @@ public class FragmentCompose extends FragmentBase {
try {
acc = InternetAddress.parse(cc);
if (action == R.id.action_send)
for (InternetAddress address : acc) {
for (InternetAddress address : acc)
address.validate();
lookup(address, context);
}
if (lookup_mx)
ConnectionHelper.lookup(acc, context);
} catch (AddressException ex) {
throw new AddressException(context.getString(R.string.title_address_parse_error,
Helper.ellipsize(cc, ADDRESS_ELLIPSIZE), ex.getMessage()));
@ -2496,10 +2495,10 @@ public class FragmentCompose extends FragmentBase {
try {
abcc = InternetAddress.parse(bcc);
if (action == R.id.action_send)
for (InternetAddress address : abcc) {
for (InternetAddress address : abcc)
address.validate();
lookup(address, context);
}
if (lookup_mx)
ConnectionHelper.lookup(abcc, context);
} catch (AddressException ex) {
throw new AddressException(context.getString(R.string.title_address_parse_error,
Helper.ellipsize(bcc, ADDRESS_ELLIPSIZE), ex.getMessage()));
@ -2675,7 +2674,6 @@ public class FragmentCompose extends FragmentBase {
db.attachment().setMessage(attachment.id, draft.id);
// Delay sending message
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int send_delayed = prefs.getInt("send_delayed", 0);
if (draft.ui_snoozed == null && send_delayed != 0) {
draft.ui_snoozed = new Date().getTime() + send_delayed * 1000L;
@ -2756,30 +2754,6 @@ public class FragmentCompose extends FragmentBase {
Helper.unexpectedError(getFragmentManager(), ex);
}
private void lookup(InternetAddress address, Context context) throws TextParseException, UnknownHostException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean lookup_mx = prefs.getBoolean("lookup_mx", false);
if (!lookup_mx)
return;
String email = address.getAddress();
if (email == null || !email.contains("@"))
return;
String domain = email.split("@")[1];
Lookup lookup = new Lookup(domain, Type.MX);
SimpleResolver resolver = new SimpleResolver(ConnectionHelper.getDnsServer(context));
lookup.setResolver(resolver);
Log.i("Lookup MX=" + domain + " @" + resolver.getAddress());
lookup.run();
if (lookup.getResult() == Lookup.HOST_NOT_FOUND ||
lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
Log.i("Lookup MX=" + domain + " result=" + lookup.getErrorString());
throw new IllegalArgumentException(context.getString(R.string.title_no_server, domain));
}
}
private String getActionName(int id) {
switch (id) {
case R.id.action_delete:

@ -59,10 +59,11 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
private SwitchCompat swDeleteUnseen;
private SwitchCompat swSyncKept;
private SwitchCompat swSyncFolders;
private SwitchCompat swCheckMx;
private final static String[] RESET_OPTIONS = new String[]{
"enabled", "poll_interval", "schedule", "schedule_start", "schedule_end",
"sync_unseen", "sync_flagged", "delete_unseen", "sync_kept", "sync_folders"
"sync_unseen", "sync_flagged", "delete_unseen", "sync_kept", "sync_folders", "check_mx"
};
@Override
@ -86,6 +87,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
swDeleteUnseen = view.findViewById(R.id.swDeleteUnseen);
swSyncKept = view.findViewById(R.id.swSyncKept);
swSyncFolders = view.findViewById(R.id.swSyncFolders);
swCheckMx = view.findViewById(R.id.swCheckMx);
setOptions();
@ -200,6 +202,13 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
}
});
swCheckMx.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("check_mx", checked).apply();
}
});
PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this);
return view;
@ -268,6 +277,7 @@ public class FragmentOptionsSynchronize extends FragmentBase implements SharedPr
swDeleteUnseen.setChecked(prefs.getBoolean("delete_unseen", false));
swSyncKept.setChecked(prefs.getBoolean("sync_kept", true));
swSyncFolders.setChecked(prefs.getBoolean("sync_folders", true));
swCheckMx.setChecked(prefs.getBoolean("check_mx", false));
}
private String formatHour(Context context, int minutes) {

@ -237,6 +237,42 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swSyncFolders" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swCheckMx"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_check_mx"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSyncFolders"
app:switchPadding="12dp" />
<TextView
android:id="@+id/tvLookupMxHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_lookup_mx_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swCheckMx" />
<TextView
android:id="@+id/tvDelayHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_sync_delay_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?attr/colorWarning"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvLookupMxHint" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -269,10 +269,12 @@
<string name="title_advanced_manual_hint">If synchronization is disabled, it is still possible to synchronize manually by pulling down the message list. This will synchronize messages and execute operations for a limited time.</string>
<string name="title_advanced_poll_hint">Synchronizing periodically will compare local and remote messages each and every time, which is an expensive operation possibly resulting in extra battery usage, especially when there are a lot of messages to synchronize. Always synchronizing will avoid this by continuous listening for changes only.</string>
<string name="title_advanced_schedule_hint">Tap on a time to set a time</string>
<string name="title_advanced_check_mx">Check sender email addresses on synchronizing messages</string>
<string name="title_advanced_sync_kept_hint">This will transfer extra data and use extra battery power, especially if there are a lot of messages kept on the device</string>
<string name="title_advanced_sync_folders_hint">Disabling this will reduce data and battery usage somewhat, but will disable updating the list of folders too</string>
<string name="title_advanced_lookup_mx_hint">This will check if DNS MX records exist</string>
<string name="title_advanced_sync_delay_hint">This will delay synchronizing messages</string>
<string name="title_advanced_metered_hint">Metered connections are generally mobile connections or paid Wi-Fi hotspots</string>
<string name="title_advanced_metered_warning">Disabling this option will disable receiving and sending messages on mobile internet connections</string>

Loading…
Cancel
Save