Exchange authenticaton and setup overhaul from Kris Wong

This commit is contained in:
Jesse Vincent 2010-10-24 01:03:29 +00:00
parent 07adaaee4d
commit c216f42eb3
6 changed files with 427 additions and 473 deletions

View File

@ -350,6 +350,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_setup_check_settings_retr_info_msg">Retrieving account information\u2026</string>
<string name="account_setup_check_settings_check_incoming_msg">Checking incoming server settings\u2026</string>
<string name="account_setup_check_settings_check_outgoing_msg">Checking outgoing server settings\u2026</string>
<string name="account_setup_check_settings_authenticate">Authenticating\u2026</string>
<string name="account_setup_check_settings_fetch">Fetching account settings\u2026</string>
<string name="account_setup_check_settings_finishing_msg">Finishing\u2026</string>
<string name="account_setup_check_settings_canceling_msg">Canceling\u2026</string>
@ -364,7 +366,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="account_setup_account_type_instructions">What type of account is this?</string>
<string name="account_setup_account_type_pop_action">POP3</string>
<string name="account_setup_account_type_imap_action">IMAP</string>
<string name="account_setup_account_type_webdav_action">WebDAV (Exchange)</string>
<string name="account_setup_account_type_webdav_action">Exchange (WebDAV)</string>
<string name="account_setup_incoming_title">Incoming server settings</string>
<string name="account_setup_incoming_username_label">Username</string>

View File

@ -889,7 +889,9 @@ public class MessageView extends K9Activity implements OnClickListener
if (K9.mobileOptimizedLayout())
{
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
} else {
}
else
{
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
}

View File

@ -22,6 +22,8 @@ import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.TrustManagerFactory;
import com.fsck.k9.mail.store.WebDavStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
@ -106,13 +108,24 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
}
if (mCheckIncoming)
{
setMessage(R.string.account_setup_check_settings_check_incoming_msg);
store = mAccount.getRemoteStore();
if (store instanceof WebDavStore)
{
setMessage(R.string.account_setup_check_settings_authenticate);
}
else
{
setMessage(R.string.account_setup_check_settings_check_incoming_msg);
}
store.checkSettings();
MessagingController.getInstance(getApplication()).listFolders(mAccount, true, null);
if (store instanceof WebDavStore)
{
setMessage(R.string.account_setup_check_settings_fetch);
}
MessagingController.getInstance(getApplication()).listFoldersSynchronous(mAccount, true, null);
MessagingController.getInstance(getApplication()).synchronizeMailbox(mAccount, K9.INBOX , null, null);
}
if (mDestroyed)
{
@ -125,7 +138,10 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
}
if (mCheckOutgoing)
{
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
if (!(mAccount.getRemoteStore() instanceof WebDavStore))
{
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
}
Transport transport = Transport.getInstance(mAccount);
transport.close();
transport.open();

View File

@ -409,61 +409,79 @@ public class MessagingController implements Runnable
{
public void run()
{
for (MessagingListener l : getListeners(listener))
{
l.listFoldersStarted(account);
}
List<? extends Folder> localFolders = null;
try
{
Store localStore = account.getLocalStore();
localFolders = localStore.getPersonalNamespaces(false);
Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
if (refreshRemote || localFolders == null || localFolders.size() == 0)
{
doRefreshRemote(account, listener);
return;
}
for (MessagingListener l : getListeners(listener))
{
l.listFolders(account, folderArray);
}
}
catch (Exception e)
{
for (MessagingListener l : getListeners(listener))
{
l.listFoldersFailed(account, e.getMessage());
}
addErrorMessage(account, null, e);
return;
}
finally
{
if (localFolders != null)
{
for (Folder localFolder : localFolders)
{
if (localFolder != null)
{
localFolder.close();
}
}
}
}
for (MessagingListener l : getListeners(listener))
{
l.listFoldersFinished(account);
}
listFoldersSynchronous(account, refreshRemote, listener);
}
});
}
/**
* Lists folders that are available locally and remotely. This method calls
* listFoldersCallback for local folders before it returns, and then for
* remote folders at some later point. If there are no local folders
* includeRemote is forced by this method. This method is called in the
* foreground.
* TODO this needs to cache the remote folder list
*
* @param account
* @param includeRemote
* @param listener
* @throws MessagingException
*/
public void listFoldersSynchronous(final Account account, final boolean refreshRemote, final MessagingListener listener)
{
for (MessagingListener l : getListeners(listener))
{
l.listFoldersStarted(account);
}
List<? extends Folder> localFolders = null;
try
{
Store localStore = account.getLocalStore();
localFolders = localStore.getPersonalNamespaces(false);
Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
if (refreshRemote || localFolders == null || localFolders.size() == 0)
{
doRefreshRemote(account, listener);
return;
}
for (MessagingListener l : getListeners(listener))
{
l.listFolders(account, folderArray);
}
}
catch (Exception e)
{
for (MessagingListener l : getListeners(listener))
{
l.listFoldersFailed(account, e.getMessage());
}
addErrorMessage(account, null, e);
return;
}
finally
{
if (localFolders != null)
{
for (Folder localFolder : localFolders)
{
if (localFolder != null)
{
localFolder.close();
}
}
}
}
for (MessagingListener l : getListeners(listener))
{
l.listFoldersFinished(account);
}
}
private void doRefreshRemote(final Account account, MessagingListener listener)
{
put("doRefreshRemote", listener, new Runnable()

View File

@ -12,20 +12,19 @@ import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.transport.TrustedSocketFactory;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.xml.sax.Attributes;
@ -34,7 +33,6 @@ import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.net.ssl.SSLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@ -65,11 +63,17 @@ import java.util.zip.GZIPInputStream;
*/
public class WebDavStore extends Store
{
public static final int CONNECTION_SECURITY_NONE = 0;
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
// Security options
private static final short CONNECTION_SECURITY_NONE = 0;
private static final short CONNECTION_SECURITY_TLS_OPTIONAL = 1;
private static final short CONNECTION_SECURITY_TLS_REQUIRED = 2;
private static final short CONNECTION_SECURITY_SSL_OPTIONAL = 3;
private static final short CONNECTION_SECURITY_SSL_REQUIRED = 4;
// Authentication types
private static final short AUTH_TYPE_NONE = 0;
private static final short AUTH_TYPE_BASIC = 1;
private static final short AUTH_TYPE_FORM_BASED = 2;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.ANSWERED };
@ -77,9 +81,12 @@ public class WebDavStore extends Store
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
private int mConnectionSecurity;
private static final String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##";
private static final String DAV_MAIL_TMP_FOLDER = "drafts";
private short mConnectionSecurity;
private String mUsername; /* Stores the username for authentications */
private String alias;
private String mAlias; /* Stores the alias for the user's mailbox */
private String mPassword; /* Stores the password for authentications */
private String mUrl; /* Stores the base URL for the server */
private String mHost; /* Stores the host name for the server */
@ -89,18 +96,16 @@ public class WebDavStore extends Store
private URI mUri; /* Stores the Uniform Resource Indicator with all connection info */
private String mRedirectUrl;
private String mAuthString;
private static String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##";
private static String DAV_MAIL_TMP_FOLDER = "drafts";
private CookieStore mAuthCookies; /* Stores cookies from authentication */
private boolean mAuthenticated = false; /* Stores authentication state */
private long mLastAuth = -1; /* Stores the timestamp of last auth */
private boolean mSecure;
private WebDavHttpClient mHttpClient = null;
private HttpContext mContext = null;
private CookieStore mAuthCookies = null;
private short mAuthentication = AUTH_TYPE_NONE;
private long mLastAuth = -1;
private long mAuthTimeout = 5 * 60;
private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>();
private boolean mSecure;
private WebDavHttpClient mHttpClient = null;
/**
* webdav://user:password@server:port CONNECTION_SECURITY_NONE
@ -121,6 +126,7 @@ public class WebDavStore extends Store
{
throw new MessagingException("Invalid WebDavStore URI", use);
}
String scheme = mUri.getScheme();
if (scheme.equals("webdav"))
{
@ -157,6 +163,34 @@ public class WebDavStore extends Store
}
}
if (mUri.getUserInfo() != null)
{
try
{
String[] userInfoParts = mUri.getUserInfo().split(":");
mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8");
String userParts[] = mUsername.split("\\\\", 2);
if (userParts.length > 1)
{
mAlias = userParts[1];
}
else
{
mAlias = mUsername;
}
if (userInfoParts.length > 1)
{
mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8");
}
}
catch (UnsupportedEncodingException enc)
{
// This shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc);
}
}
String[] pathParts = mUri.getPath().split("\\|");
for (int i = 0, count = pathParts.length; i < count; i++)
@ -202,43 +236,27 @@ public class WebDavStore extends Store
}
}
}
String path = mPath;
if (path.length() > 0 && !path.startsWith("/"))
if (!this.mPath.equals("") &&
this.mPath.startsWith("/"))
{
path = "/" + mPath;
mPath = "/" + mPath;
}
this.mUrl = getRoot() + path;
if (mUri.getUserInfo() != null)
if (this.mMailboxPath == null ||
this.mMailboxPath.equals(""))
{
try
{
String[] userInfoParts = mUri.getUserInfo().split(":");
mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8");
String userParts[] = mUsername.split("/", 2);
if (userParts.length > 1)
{
alias = userParts[1];
}
else
{
alias = mUsername;
}
if (userInfoParts.length > 1)
{
mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8");
}
}
catch (UnsupportedEncodingException enc)
{
// This shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc);
}
this.mMailboxPath = "/Exchange/" + this.mAlias;
}
mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword);
else if (!this.mMailboxPath.startsWith("/"))
{
mMailboxPath = "/" + mMailboxPath;
}
this.mUrl = getRoot() + mPath + mMailboxPath;
this.mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
this.mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword);
}
private String getRoot()
@ -259,11 +277,10 @@ public class WebDavStore extends Store
return root;
}
@Override
public void checkSettings() throws MessagingException
{
Log.e(K9.LOG_TAG, "WebDavStore.checkSettings() not implemented");
authenticate();
}
@Override
@ -531,16 +548,58 @@ public class WebDavStore extends Store
*/
/**
* Performs Form Based authentication regardless of the current
* authentication state
* Determines which type of authentication Exchange is using and
* authenticates appropriately.
* @throws MessagingException
*/
public void authenticate() throws MessagingException
public boolean authenticate() throws MessagingException
{
try
{
doFBA();
//this.mAuthCookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl);
if (mAuthentication == AUTH_TYPE_NONE)
{
ConnectionInfo info = doInitialConnection();
if (info.requiredAuthType == AUTH_TYPE_BASIC)
{
HttpGeneric request = new HttpGeneric(mUrl);
request.setMethod("GET");
request.setHeader("Authorization", mAuthString);
WebDavHttpClient httpClient = new WebDavHttpClient();
HttpResponse response = httpClient.executeOverride(request, mContext);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode < 300)
{
mAuthentication = AUTH_TYPE_BASIC;
mLastAuth = System.currentTimeMillis() / 1000;
}
else if (statusCode == 401)
{
throw new MessagingException("Invalid username or password for authentication.");
}
else
{
throw new MessagingException("Error with code " + response.getStatusLine().getStatusCode() +
" during request processing: " + response.getStatusLine().toString());
}
}
else if (info.requiredAuthType == AUTH_TYPE_FORM_BASED)
{
doFBA(info);
}
}
else if (mAuthentication == AUTH_TYPE_BASIC)
{
// Nothing to do, we authenticate with every request when
// using basic authentication.
}
else if (mAuthentication == AUTH_TYPE_FORM_BASED)
{
// Our cookie expired, re-authenticate.
doFBA(null);
}
}
catch (IOException ioe)
{
@ -548,143 +607,154 @@ public class WebDavStore extends Store
throw new MessagingException("Error during authentication", ioe);
}
if (this.mAuthCookies == null)
{
this.mAuthenticated = false;
}
else
{
this.mAuthenticated = true;
this.mLastAuth = System.currentTimeMillis()/1000;
}
return mAuthentication != AUTH_TYPE_NONE;
}
/**
* Determines if a new authentication is needed.
* Returns true if new authentication is needed.
*/
public boolean needAuth()
{
boolean status = false;
long currentTime = -1;
if (!this.mAuthenticated)
{
status = true;
}
currentTime = System.currentTimeMillis()/1000;
if ((currentTime - this.mLastAuth) > (this.mAuthTimeout))
{
status = true;
}
return status;
}
public static String getHttpRequestResponse(HttpEntity request, HttpEntity response) throws IllegalStateException, IOException
{
String responseText = "";
String requestText = "";
if (response != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(WebDavHttpClient.getUngzippedContent(response)), 8192);
String tempText = "";
while ((tempText = reader.readLine()) != null)
{
responseText += tempText;
}
}
if (request != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(WebDavHttpClient.getUngzippedContent(response)), 8192);
String tempText = "";
while ((tempText = reader.readLine()) != null)
{
requestText += tempText;
}
requestText = requestText.replaceAll("password=.*?&", "password=(omitted)&");
}
return "Request: " + requestText +
"\n\nResponse: " + responseText;
}
/**
* Performs the Form Based Authentication
* Returns the CookieStore object for later use or null
* Makes the initial connection to Exchange for authentication.
* Determines the type of authentication necessary for the server.
* @throws MessagingException
*/
public void doFBA() throws IOException, MessagingException
private ConnectionInfo doInitialConnection() throws MessagingException
{
/* public CookieStore doAuthentication(String username, String password,
String url) throws IOException, MessagingException {*/
String authPath;
String url = this.mUrl;
String username = this.mUsername;
String password = this.mPassword;
String[] urlParts = url.split("/");
String finalUrl = "";
String loginUrl = "";
String destinationUrl = "";
// For our initial connection we are sending an empty GET request to
// the configured URL, which should be in the following form:
// https://mail.server.com/Exchange/alias
//
// Possible status codes include:
// 401 - the server uses basic authentication
// 30x - the server is trying to redirect us to an OWA login
// 20x - success
//
// The latter two indicate form-based authentication.
ConnectionInfo info = new ConnectionInfo();
if (this.mAuthPath != null &&
!this.mAuthPath.equals("") &&
!this.mAuthPath.equals("/"))
{
authPath = this.mAuthPath;
}
else
{
authPath = "/exchweb/bin/auth/owaauth.dll";
}
WebDavHttpClient httpClient = getHttpClient();
for (int i = 0; i <= 2; i++)
{
if (i != 0)
{
finalUrl = finalUrl + "/" + urlParts[i];
}
else
{
finalUrl = urlParts[i];
}
}
if (finalUrl.equals(""))
{
throw new MessagingException("doFBA failed, unable to construct URL to post login credentials to.");
}
loginUrl = finalUrl + authPath;
HttpGeneric request = new HttpGeneric(mUrl);
request.setMethod("GET");
try
{
/* Browser Client */
WebDavHttpClient httpclient = mHttpClient;
HttpResponse response = httpClient.executeOverride(request, mContext);
info.statusCode = response.getStatusLine().getStatusCode();
/**
* This is in a separate block because I really don't like how it's done.
* This basically scrapes the OWA login page for the form submission URL.
* UGLY!WebDavHttpClient
* Added an if-check to see if there's a user supplied authentication path for FBA
*/
if (this.mAuthPath == null ||
this.mAuthPath.equals("") ||
this.mAuthPath.equals("/"))
if (info.statusCode == 401)
{
// 401 is the "Unauthorized" status code, meaning the server wants
// an authentication header for basic authentication.
info.requiredAuthType = AUTH_TYPE_BASIC;
}
else if ((info.statusCode >= 200 && info.statusCode < 300) || // Success
(info.statusCode >= 300 && info.statusCode < 400) || // Redirect
(info.statusCode == 440)) // Unauthorized
httpclient.addRequestInterceptor(new HttpRequestInterceptor()
{
// We will handle all 3 situations the same. First we take an educated
// guess at where the authorization DLL is located. If this is this
// doesn't work, then we'll use the redirection URL for OWA login given
// to use by exchange. We can use this to scrape the location of the
// authorization URL.
info.requiredAuthType = AUTH_TYPE_FORM_BASED;
if (mAuthPath != null && !mAuthPath.equals(""))
{
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException
{
mRedirectUrl = ((HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST)).toURI() + request.getRequestLine().getUri();
}
});
HashMap<String, String> headers = new HashMap<String, String>();
InputStream istream = sendRequest(finalUrl, "GET", null, headers, false);
// The user specified their own authentication path, use that.
info.guessedAuthUrl = getRoot() + mAuthPath;
}
else
{
// Use the default path to the authentication dll.
info.guessedAuthUrl = getRoot() + "/exchweb/bin/auth/owaauth.dll";
}
// Determine where the server is trying to redirect us.
Header location = response.getFirstHeader("Location");
if (location != null)
{
info.redirectUrl = location.getValue();
}
}
else
{
throw new IOException("Error with code " + info.statusCode + " during request processing: "+
response.getStatusLine().toString());
}
}
catch(IOException ioe)
{
Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
throw new MessagingException("IOException", ioe);
}
return info;
}
/**
* Performs form-based authentication.
* @throws MessagingException
*/
public void doFBA(ConnectionInfo info) throws IOException, MessagingException
{
WebDavHttpClient httpClient = getHttpClient();
HttpGeneric request = new HttpGeneric(info.guessedAuthUrl);
request.setMethod("POST");
// Build the POST data.
ArrayList<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
pairs.add(new BasicNameValuePair("destination", mUrl));
pairs.add(new BasicNameValuePair("username", mUsername));
pairs.add(new BasicNameValuePair("password", mPassword));
pairs.add(new BasicNameValuePair("flags", "0"));
pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
pairs.add(new BasicNameValuePair("trusted", "0"));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs);
request.setEntity(formEntity);
HttpResponse response = httpClient.executeOverride(request, mContext);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode < 300)
{
// Success, we're logged on and an authentication cookie should
// have already been added to mAuthCookies for us.
}
else if (statusCode == 404)
{
// The resource was not found, which means we need to get tricky
// about finding the correct login path.
// Send a request to our original redirect URL, and scrape the
// login path from the returned page.
httpClient.addRequestInterceptor(new HttpRequestInterceptor()
{
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException
{
mRedirectUrl = ((HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST)).toURI() +
request.getRequestLine().getUri();
}
});
String loginUrl = "";
if (info != null)
{
loginUrl = info.redirectUrl;
}
else if (mRedirectUrl != null && !mRedirectUrl.equals(""))
{
loginUrl = mRedirectUrl;
}
else
{
throw new MessagingException("No valid login URL available for form-based authentication.");
}
try
{
InputStream istream = sendRequest(loginUrl, "GET", null, null, false);
if (istream != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 4096);
@ -705,104 +775,36 @@ public class WebDavStore extends Store
mRedirectUrl = mRedirectUrl.substring(0, mRedirectUrl.lastIndexOf('?'));
mRedirectUrl = mRedirectUrl.substring(0, mRedirectUrl.lastIndexOf('/'));
loginUrl = mRedirectUrl + "/" + tagParts[1];
this.mAuthPath = "/" + tagParts[1];
this.mAuthPath = new URI(loginUrl).getPath();
}
else
{
loginUrl = finalUrl + tagParts[1];
this.mAuthPath = "/" + tagParts[1];
}
}
if (tempText.indexOf("destination") >= 0)
{
String[] tagParts = tempText.split("value");
if (tagParts[1] != null)
{
String[] valueParts = tagParts[1].split("\"");
destinationUrl = valueParts[1];
matched = true;
loginUrl = getRoot() + tagParts[1];
this.mAuthPath = new URI(loginUrl).getPath();
}
}
}
istream.close();
// Now retry the login using our scraped login URL.
request = new HttpGeneric(loginUrl);
request.setMethod("POST");
request.setEntity(formEntity);
httpClient.executeOverride(request, mContext);
}
}
/** Build the POST data to use */
ArrayList<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
pairs.add(new BasicNameValuePair("username", username));
pairs.add(new BasicNameValuePair("password", password));
if (this.mMailboxPath != null &&
!this.mMailboxPath.equals(""))
catch(URISyntaxException use)
{
pairs.add(new BasicNameValuePair("destination", finalUrl + this.mMailboxPath));
}
else if (destinationUrl != null &&
!destinationUrl.equals(""))
{
pairs.add(new BasicNameValuePair("destination", destinationUrl));
}
else
{
pairs.add(new BasicNameValuePair("destination", "/"));
}
pairs.add(new BasicNameValuePair("flags", "0"));
pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
pairs.add(new BasicNameValuePair("trusted", "0"));
try
{
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs);
HashMap<String, String> headers = new HashMap<String, String>();
String tempUrl = "";
InputStream istream = sendRequest(loginUrl, "POST", formEntity, headers, false);
/** Get the URL for the mailbox and set it for the store */
if (istream != null)
{
BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 8192);
String tempText = "";
while ((tempText = reader.readLine()) != null)
{
if (tempText.indexOf("BASE href") >= 0)
{
String[] tagParts = tempText.split("\"");
tempUrl = tagParts[1];
}
}
}
if (this.mMailboxPath != null &&
!this.mMailboxPath.equals(""))
{
this.mUrl = finalUrl + "/" + this.mMailboxPath + "/";
}
else if (tempUrl.equals(""))
{
this.mUrl = finalUrl + "/Exchange/" + this.alias + "/";
}
else
{
this.mUrl = tempUrl;
}
}
catch (UnsupportedEncodingException uee)
{
Log.e(K9.LOG_TAG, "Error encoding POST data for authentication: " + uee + "\nTrace: " + processException(uee));
throw new MessagingException("Error encoding POST data for authentication", uee);
throw new MessagingException("An invalid login URL was detected: " + loginUrl);
}
}
catch (SSLException e)
if (mAuthCookies != null && !mAuthCookies.getCookies().isEmpty())
{
throw new CertificateValidationException(e.getMessage(), e);
mAuthentication = AUTH_TYPE_FORM_BASED;
mLastAuth = System.currentTimeMillis() / 1000;
}
this.mAuthenticated = true;
}
public CookieStore getAuthCookies()
@ -812,7 +814,7 @@ public class WebDavStore extends Store
public String getAlias()
{
return alias;
return mAlias;
}
public String getUrl()
@ -822,100 +824,38 @@ public class WebDavStore extends Store
public WebDavHttpClient getHttpClient() throws MessagingException
{
SchemeRegistry reg;
Scheme s;
boolean needAuth = false;
if (mHttpClient == null)
{
mHttpClient = new WebDavHttpClient();
needAuth = true;
}
reg = mHttpClient.getConnectionManager().getSchemeRegistry();
try
{
// Log.i(K9.LOG_TAG, "getHttpClient mHost = " + mHost);
s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443);
}
catch (NoSuchAlgorithmException nsa)
{
Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
throw new MessagingException("NoSuchAlgorithmException in getHttpClient: " + nsa);
}
catch (KeyManagementException kme)
{
Log.e(K9.LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
throw new MessagingException("KeyManagementException in getHttpClient: " + kme);
}
reg.register(s);
// Setup a cookie store for forms-based authentication.
mContext = new BasicHttpContext();
mAuthCookies = new BasicCookieStore();
mContext.setAttribute(ClientContext.COOKIE_STORE, mAuthCookies);
if (needAuth)
{
HashMap<String, String> headers = new HashMap<String, String>();
processRequest(this.mUrl, "GET", null, headers, false);
}
/*
if (needAuth()) {
if (!checkAuth()) {
try {
CookieStore cookies = mHttpClient.getCookieStore();
cookies.clear();
mHttpClient.setCookieStore(cookies);
cookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl);
if (cookies != null) {
this.mAuthenticated = true;
this.mLastAuth = System.currentTimeMillis()/1000;
}
mHttpClient.setCookieStore(cookies);
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
}
} else {
Credentials creds = new UsernamePasswordCredentials(mUsername, mPassword);
CredentialsProvider credsProvider = mHttpClient.getCredentialsProvider();
credsProvider.setCredentials(new AuthScope(mHost, 80, AuthScope.ANY_REALM), creds);
credsProvider.setCredentials(new AuthScope(mHost, 443, AuthScope.ANY_REALM), creds);
credsProvider.setCredentials(new AuthScope(mHost, mUri.getPort(), AuthScope.ANY_REALM), creds);
mHttpClient.setCredentialsProvider(credsProvider);
// Assume we're authenticated and ok here since the checkAuth() was 401 and we've now set the credentials
this.mAuthenticated = true;
this.mLastAuth = System.currentTimeMillis()/1000;
SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
try
{
Scheme s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443);
reg.register(s);
}
catch (NoSuchAlgorithmException nsa)
{
Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
throw new MessagingException("NoSuchAlgorithmException in getHttpClient: " + nsa);
}
catch (KeyManagementException kme)
{
Log.e(K9.LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
throw new MessagingException("KeyManagementException in getHttpClient: " + kme);
}
}
*/
return mHttpClient;
}
public WebDavHttpClient getTrustedHttpClient() throws KeyManagementException, NoSuchAlgorithmException
{
if (mHttpClient == null)
{
mHttpClient = new WebDavHttpClient();
SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
Scheme s = new Scheme("https",new TrustedSocketFactory(mHost,mSecure),443);
reg.register(s);
//Add credentials for NTLM/Digest/Basic Auth
Credentials creds = new UsernamePasswordCredentials(mUsername, mPassword);
CredentialsProvider credsProvider = mHttpClient.getCredentialsProvider();
// setting AuthScope for 80 and 443, in case we end up getting redirected
// from 80 to 443.
credsProvider.setCredentials(new AuthScope(mHost, 80, AuthScope.ANY_REALM), creds);
credsProvider.setCredentials(new AuthScope(mHost, 443, AuthScope.ANY_REALM), creds);
credsProvider.setCredentials(new AuthScope(mHost, mUri.getPort(), AuthScope.ANY_REALM), creds);
mHttpClient.setCredentialsProvider(credsProvider);
}
return mHttpClient;
}
private InputStream sendRequest(String url, String method, StringEntity messageBody, HashMap<String, String> headers, boolean tryAuth)
throws MessagingException
{
WebDavHttpClient httpclient;
InputStream istream = null;
if (url == null ||
@ -924,7 +864,7 @@ public class WebDavStore extends Store
return istream;
}
httpclient = getHttpClient();
WebDavHttpClient httpclient = getHttpClient();
try
{
@ -943,56 +883,46 @@ public class WebDavStore extends Store
httpmethod.setHeader(headerName, headers.get(headerName));
}
if (mAuthString != null && mAuthenticated)
if (mAuthentication == AUTH_TYPE_NONE)
{
if (!tryAuth || !authenticate())
{
throw new MessagingException("Unable to authenticate in sendRequest().");
}
}
else if (mAuthentication == AUTH_TYPE_BASIC)
{
httpmethod.setHeader("Authorization", mAuthString);
}
httpmethod.setMethod(method);
response = httpclient.executeOverride(httpmethod);
response = httpclient.executeOverride(httpmethod, mContext);
statusCode = response.getStatusLine().getStatusCode();
entity = response.getEntity();
if (statusCode == 401)
{
if (tryAuth)
{
mAuthenticated = true;
sendRequest(url, method, messageBody, headers, false);
}
else
{
throw new MessagingException("Invalid username or password for Basic authentication");
}
throw new MessagingException("Invalid username or password for Basic authentication.");
}
else if (statusCode == 440)
{
if (tryAuth)
if (tryAuth && mAuthentication == AUTH_TYPE_FORM_BASED)
{
doFBA();
// Our cookie expired, re-authenticate.
doFBA(null);
sendRequest(url, method, messageBody, headers, false);
}
else
{
throw new MessagingException("Authentication failure in sendRequest");
throw new MessagingException("Authentication failure in sendRequest().");
}
}
else if (statusCode < 200 ||
statusCode >= 300)
else if (statusCode < 200 || statusCode >= 300)
{
throw new IOException("Error with code " + statusCode + " during request processing: "+
throw new IOException("Error with code " + statusCode + " during request processing: " +
response.getStatusLine().toString());
}
else
{
if (tryAuth &&
!mAuthenticated)
{
doFBA();
sendRequest(url, method, messageBody, headers, false);
}
}
if (entity != null)
{
@ -1284,10 +1214,9 @@ public class WebDavStore extends Store
Log.i(K9.LOG_TAG, "Moving " + messages.length + " messages to " + destFolder.mFolderUrl);
processRequest(mFolderUrl, action, messageBody, headers, false);
}
private int getMessageCount(boolean read, CookieStore authCookies) throws MessagingException
private int getMessageCount(boolean read) throws MessagingException
{
String isRead;
int messageCount = 0;
@ -1319,8 +1248,7 @@ public class WebDavStore extends Store
public int getMessageCount() throws MessagingException
{
open(OpenMode.READ_WRITE);
this.mMessageCount = getMessageCount(true, WebDavStore.this.mAuthCookies);
this.mMessageCount = getMessageCount(true);
return this.mMessageCount;
}
@ -1328,10 +1256,10 @@ public class WebDavStore extends Store
public int getUnreadMessageCount() throws MessagingException
{
open(OpenMode.READ_WRITE);
this.mUnreadMessageCount = getMessageCount(false, WebDavStore.this.mAuthCookies);
this.mUnreadMessageCount = getMessageCount(false);
return this.mUnreadMessageCount;
}
@Override
public int getFlaggedMessageCount() throws MessagingException
{
@ -1586,11 +1514,11 @@ public class WebDavStore extends Store
HttpEntity entity;
httpget.setHeader("translate", "f");
if (mAuthString != null && mAuthenticated)
if (mAuthentication == AUTH_TYPE_BASIC)
{
httpget.setHeader("Authorization", mAuthString);
}
response = httpclient.executeOverride(httpget);
response = httpclient.executeOverride(httpget, mContext);
statusCode = response.getStatusLine().getStatusCode();
@ -1965,7 +1893,7 @@ public class WebDavStore extends Store
httpmethod.setHeader("Authorization", mAuthString);
}
response = httpclient.executeOverride(httpmethod);
response = httpclient.executeOverride(httpmethod, mContext);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < 200 ||
@ -2619,12 +2547,22 @@ public class WebDavStore extends Store
return responseStream;
}
public HttpResponse executeOverride(HttpUriRequest request) throws IOException
public HttpResponse executeOverride(HttpUriRequest request, HttpContext context)
throws IOException
{
modifyRequestToAcceptGzipResponse(request);
return super.execute(request);
return super.execute(request, context);
}
}
/**
* Simple data container for passing connection information.
*/
private class ConnectionInfo
{
public int statusCode;
public short requiredAuthType;
public String guessedAuthUrl;
public String redirectUrl;
}
}

View File

@ -8,41 +8,23 @@ import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.store.WebDavStore;
import java.io.OutputStream;
import java.net.Socket;
public class WebDavTransport extends Transport
{
public static final int CONNECTION_SECURITY_NONE = 0;
public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1;
public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2;
public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3;
public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4;
String host;
int mPort;
boolean mSecure;
Socket mSocket;
PeekableInputStream mIn;
OutputStream mOut;
private WebDavStore store;
/**
* webdav://user:password@server:port CONNECTION_SECURITY_NONE
* webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
*
* @param _uri
*/
public WebDavTransport(Account account) throws MessagingException
{
store = new WebDavStore(account);
if (account.getRemoteStore() instanceof WebDavStore)
{
store = (WebDavStore) account.getRemoteStore();
}
else
{
store = new WebDavStore(account);
}
if (K9.DEBUG)
Log.d(K9.LOG_TAG, ">>> New WebDavTransport creation complete");
}
@ -64,10 +46,6 @@ public class WebDavTransport extends Transport
@Override
public void sendMessage(Message message) throws MessagingException
{
store.sendMessages(new Message[] { message });
}
}