1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-11 20:15:03 -05:00

Added support for DNS MX redirection in autoconfiguration.

Reworked the gui for the autoconfiguration activity.
This commit is contained in:
sander 2011-11-23 16:55:34 +01:00 committed by Andrew Chen
parent 1c4ebf15b7
commit 5aa0dce062
3 changed files with 309 additions and 178 deletions

View File

@ -1,65 +1,76 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="match_parent"
android:orientation="vertical" > android:orientation="vertical" >
<TextView android:id="@+id/status_message" <ScrollView
android:layout_height="wrap_content" android:id="@+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/controls_group" >
<TextView
android:id="@+id/status_message"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="10dp"
android:paddingBottom="6dip" /> android:paddingBottom="6dip" />
</ScrollView>
<RelativeLayout <RelativeLayout
android:layout_below="@id/status_message" android:id="@+id/controls_group"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="158dp"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:orientation="vertical" android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"> android:orientation="vertical" >
<TextView android:id="@+id/autoconfig_warning"
android:gravity="center"
android:textColor="#C44141"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_setup_autoconfig_unsafe_connection" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentBottom="true">
<!-- kind of a hack.... need to have a look at whis this is behind the buttons -->
<ProgressBar android:id="@+id/autoconfig_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginBottom="50dp"
style="@android:style/Widget.ProgressBar.Large" />
<LinearLayout <LinearLayout
android:layout_below="@id/autoconfig_progress" android:id="@+id/linearLayout1"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:gravity="center" android:gravity="center"
android:orientation="horizontal" > android:orientation="horizontal" >
<Button android:id="@+id/autoconfig_button_cancel"
<Button
android:id="@+id/autoconfig_button_cancel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/cancel_action" /> android:text="@string/cancel_action" />
<Button android:id="@+id/autoconfig_button_next" <Button
android:drawableRight="@drawable/button_indicator_next" android:id="@+id/autoconfig_button_next"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawableRight="@drawable/button_indicator_next"
android:text="@string/next_action" /> android:text="@string/next_action" />
</LinearLayout> </LinearLayout>
</RelativeLayout>
<TextView
android:id="@+id/autoconfig_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/linearLayout1"
android:layout_alignParentLeft="true"
android:layout_marginBottom="20dp"
android:gravity="center"
android:text="@string/account_setup_autoconfig_unsafe_connection"
android:textColor="#C44141"
android:textStyle="bold" />
<ProgressBar
android:id="@+id/autoconfig_progress"
style="@android:style/Widget.ProgressBar.Large.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/autoconfig_warning"
android:layout_centerHorizontal="true" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -384,6 +384,8 @@ http://k9mail.googlecode.com/
<string name="account_setup_autoconfig_fail">failed!</string> <string name="account_setup_autoconfig_fail">failed!</string>
<string name="account_setup_autoconfig_forcemanual">No were settings found, use manual configuration to continue.</string> <string name="account_setup_autoconfig_forcemanual">No were settings found, use manual configuration to continue.</string>
<string name="account_setup_autoconfig_trynext">Searching other places for configuration settings...</string> <string name="account_setup_autoconfig_trynext">Searching other places for configuration settings...</string>
<string name="account_setup_autoconfig_trydns">Checking for DNS redirects...</string>
<string name="account_setup_autoconfig_new_domain">Retrying autoconfiguration for a new domain.</string>
<string name="account_setup_names_title">You\'re almost done!</string> <string name="account_setup_names_title">You\'re almost done!</string>
<string name="account_setup_names_instructions">Your account is set up, and mail is on its way!</string> <string name="account_setup_names_instructions">Your account is set up, and mail is on its way!</string>

View File

@ -1,11 +1,8 @@
package com.fsck.k9.activity.setup; package com.fsck.k9.activity.setup;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.*; import android.os.*;
import android.os.Process;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@ -17,8 +14,13 @@ import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.helper.configxmlparser.AutoconfigInfo; import com.fsck.k9.helper.configxmlparser.AutoconfigInfo;
import com.fsck.k9.helper.configxmlparser.ConfigurationXMLHandler; import com.fsck.k9.helper.configxmlparser.ConfigurationXMLHandler;
import com.fsck.k9.mail.store.TrustManagerFactory; import com.fsck.k9.mail.store.TrustManagerFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.Parser;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.XMLReader; import org.xml.sax.XMLReader;
@ -27,11 +29,9 @@ import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.TimeoutException; import java.util.List;
/** /**
* User: dzan * User: dzan
@ -49,6 +49,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
// location of mozilla's ispdb // location of mozilla's ispdb
private String databaseBaseUrl = "https://live.mozillamessaging.com/autoconfig/v1.1/"; private String databaseBaseUrl = "https://live.mozillamessaging.com/autoconfig/v1.1/";
private String dnsMXLookupUrl = "https://live.mozillamessaging.com/dns/mx/";
// for now there are only 2 so I just hardcode them here // for now there are only 2 so I just hardcode them here
// also note: order they are listed is the order they'll be checked // also note: order they are listed is the order they'll be checked
@ -134,17 +135,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
mPassword = getIntent().getStringExtra(PASSWORD); mPassword = getIntent().getStringExtra(PASSWORD);
mMakeDefault = getIntent().getBooleanExtra(MAKEDEFAULT, false); mMakeDefault = getIntent().getBooleanExtra(MAKEDEFAULT, false);
// The real action, in a separate thread // inform user we start autoconfig
new Thread() {
@Override
public void run() {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// declare some variables
String data = ""; // used to store downloaded xml before parsing
String tmpURL = "";
// notify the user we'll get started
setMessage(R.string.account_setup_autoconfig_info, true); setMessage(R.string.account_setup_autoconfig_info, true);
// divide the address // divide the address
@ -152,111 +143,8 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
String user = emailParts[0]; String user = emailParts[0];
String domain = emailParts[1]; String domain = emailParts[1];
/* // The real action, in a separate thread
Check if configuration data exists and if it does read in new AutoConfigurationThread(domain).start();
*/
int i = 0;
while( i < urlTemplates.size() && !bFound ){
try{
// inform the user
setMessage(urlInfoStatements.get(i),true);
// to make sure
bParseFailed = false;
bForceManual = false;
bDoneSearching = false;
// preparing the urls
if( !domain.contains("%user%") ){ // else SHIT
tmpURL = urlTemplates.get(i).replaceAll("%domain%",domain);
tmpURL = tmpURL.replaceAll("%address%",mEmailAddress);
}
data = getXMLData(new URL(tmpURL));
// might be the user cancelled by now or the app was destroyed
if (mDestroyed) return;
if (mCanceled) { finish(); return; }
if( !data.isEmpty() ){
setMessage(R.string.account_setup_autoconfig_found,false);
// parse and finish
setMessage(R.string.account_setup_autoconfig_processing,true);
parse(data);
setMessage(R.string.account_setup_autoconfig_succesful,false);
// alert user these settings might be tampered with!!! ( no https )
if( i >= UNSAFE_URL_START ) bUnsafe = true;
bFound = true;
continue;
}
}catch (SocketTimeoutException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"' ( time-out is"+TIMEOUT+" )", ex);
}catch (MalformedURLException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
}catch (UnknownHostException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
}catch (SSLPeerUnverifiedException ex){
Log.e(K9.LOG_TAG, "Error while testing settings", ex);
acceptKeyDialog(R.string.account_setup_failed_dlg_certificate_message_fmt,i,ex);
}catch (SAXException e) {
setMessage(R.string.account_setup_autoconfig_fail,false);
bParseFailed = true;
}catch (ParserConfigurationException e) {
setMessage(R.string.account_setup_autoconfig_fail,false);
bParseFailed = true;
}catch (ErrorCodeException ex) {
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '" +
tmpURL + "' site didn't respond as expected. Got code: " + ex.getErrorCode(), ex);
}catch(IOException ex) {
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
} finally {
// might be the user cancelled by now or the app was destroyed
if (mDestroyed) return;
if (mCanceled) { finish(); return; }
// check next url
++i;
// this was the last option..
if(i == urlTemplates.size() ){
bForceManual = true;
setMessage(R.string.account_setup_autoconfig_forcemanual, true);
}else{
if( bParseFailed )
setMessage(R.string.account_setup_autoconfig_trynext,true);
}
}
// no server-side config was found
closeUserInformationIfNeeded(i-1);
}
bDoneSearching = true;
runOnUiThread(new Runnable() {
public void run() {
// TODO: set appropriate warning messages in here
// 1. All good, continue
// 2. Nothing came up, must manually config.. + help?
// 3. Data did not came over HTTPS this could be UNSAFE !!!!!!
mProgressCircle.setVisibility(View.INVISIBLE);
if( bUnsafe /*&& !bForceManual*/ ) mWarningMsg.setVisibility(View.VISIBLE);
mNextButton.setEnabled(true);
if( bForceManual )
mNextButton.setText(getString(R.string.account_setup_basics_manual_setup_action));
}
});
}
}
.start();
} }
@ -272,6 +160,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
mAutoConfigInfo = parser.getAutoconfigInfo(); mAutoConfigInfo = parser.getAutoconfigInfo();
} }
/* /*
Checks if an url is available / exists Checks if an url is available / exists
*/ */
@ -300,12 +189,83 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
} }
catch (SocketTimeoutException ex) catch (SocketTimeoutException ex)
{ throw ex; } { throw ex; }
catch (ConnectException ex){
// ignore this, it just means the url doesn't exist which happens often, we test for it!
}
finally finally
{ conn.disconnect(); } { conn.disconnect(); }
return tmp; return tmp;
} }
/*
* Does an DNS MX lookup of the domain and returns a list of records.
* This uses the Mozilla webservice to do so.
*/
private List<String> doMXLookup(String domain) throws IOException{
HttpClient httpclient = new DefaultHttpClient();
HttpGet method = new HttpGet(dnsMXLookupUrl + domain);
// do request
HttpResponse response = httpclient.execute(method);
String data = EntityUtils.toString(response.getEntity());
return new ArrayList<String>(Arrays.asList(data.split("[\\r\\n]+")));
}
/*
* Does two things:
* 1. Detect the isp-domain parts of the mxServer hostnames
* 2. Filters these so we have a list of uniques also not containing the initial domainname used for mx lookup
*
* TODO:
* Speed this up! There are a lot of options...
* Use a Set instead of a list, don't rebuild tmpStr every time,...
*/
private List<String> getDomainPossibilities(List<String> mxServers, String origDomain){
List<String> filteredDomains = new ArrayList<String>();
String[] serverSplit;
String tmpStr, prevAtom;
int size = 0; // total atoms in server hostname
// number of atoms in last found domain
int parts = 2; // to begin with, minimum so never arrayoutofbound
for( String server : mxServers )
{
serverSplit = server.toLowerCase().split("\\.");
prevAtom = serverSplit[0];
// speed things up a bit ( ugly )
size = serverSplit.length;
tmpStr = "";
for( int k=size-1; k>(size-parts-1); --k) tmpStr += "."+serverSplit[k];
tmpStr = tmpStr.substring(1);
if( filteredDomains.contains(tmpStr)) continue;
// determine right domainname
for( int i = 1; i < serverSplit.length; ++i ){
// build domainstring to test
tmpStr = "";
for( int j=i; j<serverSplit.length; ++j) tmpStr += "."+serverSplit[j];
tmpStr = tmpStr.substring(1);
// we matched a as-wide-as-possible tld
if(com.fsck.k9.helper.Regex.TOP_LEVEL_DOMAIN_PATTERN.matcher(tmpStr).matches()){
tmpStr = prevAtom + "." + tmpStr;
size = 1 + (size - i);
if( !tmpStr.equals(origDomain) && !filteredDomains.contains(tmpStr) )
filteredDomains.add(tmpStr);
}
prevAtom = serverSplit[i];
}
}
return filteredDomains;
}
/* /*
Adds messages to the view, provides the user with progress reports Adds messages to the view, provides the user with progress reports
*/ */
@ -330,7 +290,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
}); });
} }
private void closeUserInformationIfNeeded(int urlNumb) { private synchronized void closeUserInformationIfNeeded(int urlNumb) {
if( bForceManual ) return; if( bForceManual ) return;
if( urlNumb == urlInfoStatements.size() - 1 if( urlNumb == urlInfoStatements.size() - 1
@ -343,6 +303,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
}); });
} }
} }
/* /*
We stop our thread We stop our thread
*/ */
@ -403,7 +364,7 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
TODO: Rework this so it changes the url counter, not restart intent TODO: Rework this so it changes the url counter, not restart intent
NOTE: It's called but doesn't work right now because for the connection the default sslfactory is yet used NOTE: It's called but doesn't work right now because for the connection the default sslfactory is yet used
*/ */
private void acceptKeyDialog(final int msgResId, final int urlNumber, final Object... args) { /* private void acceptKeyDialog(final int msgResId, final int urlNumber, final Object... args) {
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
public void run() { public void run() {
if (mDestroyed) { if (mDestroyed) {
@ -464,8 +425,165 @@ public class AccountSetupAutoConfiguration extends K9Activity implements View.On
.show(); .show();
} }
}); });
}*/
/*
* Thread class to do the autoconfiguration
*/
private class AutoConfigurationThread extends Thread{
private String mDomain;
private List<String> mDomainAlternatives;
public AutoConfigurationThread(String domain) {
super();
this.mDomain = domain;
} }
@Override
public void run() {
//android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// declare some variables
String data = ""; // used to store downloaded xml before parsing
String tmpURL = "";
int templateIndex = 0;
while( templateIndex < urlTemplates.size() && !bFound ){
try{
// inform the user
setMessage(urlInfoStatements.get(templateIndex),true);
// to make sure
bParseFailed = false;
bForceManual = false;
bDoneSearching = false;
// preparing the urls
if( !mDomain.contains("%user%") ){ // else SHIT
tmpURL = urlTemplates.get(templateIndex).replaceAll("%domain%",mDomain);
tmpURL = tmpURL.replaceAll("%address%",mEmailAddress);
}
// get the xml data
data = getXMLData(new URL(tmpURL));
// might be the user cancelled by now or the app was destroyed
if (mDestroyed) return;
if (mCanceled) { finish(); return; }
// if we really have data
if( !data.isEmpty() ){
setMessage(R.string.account_setup_autoconfig_found,false);
// parse and finish
setMessage(R.string.account_setup_autoconfig_processing,true);
parse(data);
setMessage(R.string.account_setup_autoconfig_succesful,false);
// alert user these settings might be tampered with!!! ( no https )
if( templateIndex >= UNSAFE_URL_START ) bUnsafe = true;
bFound = true;
continue;
}
}catch (SocketTimeoutException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"' ( time-out is"+TIMEOUT+" )", ex);
}catch (MalformedURLException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
}catch (UnknownHostException ex){
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
}catch (SSLPeerUnverifiedException ex){
Log.e(K9.LOG_TAG, "Error while testing settings", ex);
// TODO: use custom trust manager so this exception could get thrown
//acceptKeyDialog(R.string.account_setup_failed_dlg_certificate_message_fmt,i,ex);
}catch (SAXException e) {
setMessage(R.string.account_setup_autoconfig_fail,false);
bParseFailed = true;
}catch (ParserConfigurationException e) {
setMessage(R.string.account_setup_autoconfig_fail,false);
bParseFailed = true;
}catch (ErrorCodeException ex) {
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '" +
tmpURL + "' site didn't respond as expected. Got code: " + ex.getErrorCode(), ex);
}catch(IOException ex) {
Log.e(K9.LOG_TAG, "Error while attempting auto-configuration with url '"+
tmpURL+"'", ex);
} finally {
// might be the user cancelled by now or the app was destroyed
if (mDestroyed) return;
if (mCanceled) { finish(); return; }
// check next url
++templateIndex;
if( !bFound ) closeUserInformationIfNeeded(templateIndex-1);
// did parsing fail? tell user
if( bParseFailed )
setMessage(R.string.account_setup_autoconfig_trynext,true);
// this is the last domain to try in the list
if( templateIndex == urlTemplates.size()){
// we can still try DNS MX
if( mDomainAlternatives == null ){
try {
setMessage(R.string.account_setup_autoconfig_trydns, true);
mDomainAlternatives = getDomainPossibilities(doMXLookup(mDomain), mDomain);
if( mDomainAlternatives.size() > 0 )
setMessage(R.string.account_setup_autoconfig_found, false);
else
setMessage(R.string.account_setup_autoconfig_missing, false);
} catch (IOException e) {
mDomainAlternatives = new ArrayList<String>(); // setting empty list = no options left
setMessage(R.string.account_setup_autoconfig_missing, false);
Log.e(K9.LOG_TAG, "Error while getting DNS MX data in autoconfiguration", e);
}
}
// still domains remaining to try, restart whole lookup with new domain
if( mDomainAlternatives.size() > 0 ){
mDomain = mDomainAlternatives.get(0);
mDomainAlternatives.remove(0);
templateIndex = 0;
setMessage(R.string.account_setup_autoconfig_new_domain, true);
// out of options... manual configuration
}else{
bForceManual = true;
setMessage(R.string.account_setup_autoconfig_forcemanual, true);
}
}
}
}
// remember we've searched already
bDoneSearching = true;
// update ui state
runOnUiThread(new Runnable() {
public void run() {
// hide progress circle & enable button for any case
mProgressCircle.setVisibility(View.INVISIBLE);
mNextButton.setEnabled(true);
// 1. All good, continue
// all is fine
// 2. Nothing came up, must manually config.. + help?
if( bForceManual ) mNextButton.setText(getString(R.string.account_setup_basics_manual_setup_action));
// 3. Data did not came over HTTPS this could be UNSAFE !!!!!!
if( bUnsafe ) mWarningMsg.setVisibility(View.VISIBLE);
}
});
}
}
/* /*
Small custom exception to pass http response codes around Small custom exception to pass http response codes around