summaryrefslogtreecommitdiff
path: root/java/com/android/voicemailomtp/mail/store/ImapConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/voicemailomtp/mail/store/ImapConnection.java')
-rw-r--r--java/com/android/voicemailomtp/mail/store/ImapConnection.java413
1 files changed, 0 insertions, 413 deletions
diff --git a/java/com/android/voicemailomtp/mail/store/ImapConnection.java b/java/com/android/voicemailomtp/mail/store/ImapConnection.java
deleted file mode 100644
index 61dcf1281..000000000
--- a/java/com/android/voicemailomtp/mail/store/ImapConnection.java
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.voicemailomtp.mail.store;
-
-import android.util.ArraySet;
-import android.util.Base64;
-import com.android.voicemailomtp.mail.AuthenticationFailedException;
-import com.android.voicemailomtp.mail.CertificateValidationException;
-import com.android.voicemailomtp.mail.MailTransport;
-import com.android.voicemailomtp.mail.MessagingException;
-import com.android.voicemailomtp.mail.store.ImapStore.ImapException;
-import com.android.voicemailomtp.mail.store.imap.DigestMd5Utils;
-import com.android.voicemailomtp.mail.store.imap.ImapConstants;
-import com.android.voicemailomtp.mail.store.imap.ImapResponse;
-import com.android.voicemailomtp.mail.store.imap.ImapResponseParser;
-import com.android.voicemailomtp.mail.store.imap.ImapUtility;
-import com.android.voicemailomtp.mail.utils.LogUtils;
-import com.android.voicemailomtp.OmtpEvents;
-import com.android.voicemailomtp.VvmLog;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.SSLException;
-
-/**
- * A cacheable class that stores the details for a single IMAP connection.
- */
-public class ImapConnection {
- private final String TAG = "ImapConnection";
-
- private String mLoginPhrase;
- private ImapStore mImapStore;
- private MailTransport mTransport;
- private ImapResponseParser mParser;
- private Set<String> mCapabilities = new ArraySet<>();
-
- static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
-
- /**
- * Next tag to use. All connections associated to the same ImapStore instance share the same
- * counter to make tests simpler.
- * (Some of the tests involve multiple connections but only have a single counter to track the
- * tag.)
- */
- private final AtomicInteger mNextCommandTag = new AtomicInteger(0);
-
- ImapConnection(ImapStore store) {
- setStore(store);
- }
-
- void setStore(ImapStore store) {
- // TODO: maybe we should throw an exception if the connection is not closed here,
- // if it's not currently closed, then we won't reopen it, so if the credentials have
- // changed, the connection will not be reestablished.
- mImapStore = store;
- mLoginPhrase = null;
- }
-
- /**
- * Generates and returns the phrase to be used for authentication. This will be a LOGIN with
- * username and password.
- *
- * @return the login command string to sent to the IMAP server
- */
- String getLoginPhrase() {
- if (mLoginPhrase == null) {
- if (mImapStore.getUsername() != null && mImapStore.getPassword() != null) {
- // build the LOGIN string once (instead of over-and-over again.)
- // apply the quoting here around the built-up password
- mLoginPhrase = ImapConstants.LOGIN + " " + mImapStore.getUsername() + " "
- + ImapUtility.imapQuoted(mImapStore.getPassword());
- }
- }
- return mLoginPhrase;
- }
-
- public void open() throws IOException, MessagingException {
- if (mTransport != null && mTransport.isOpen()) {
- return;
- }
-
- try {
- // copy configuration into a clean transport, if necessary
- if (mTransport == null) {
- mTransport = mImapStore.cloneTransport();
- }
-
- mTransport.open();
-
- createParser();
-
- // The server should greet us with something like
- // * OK IMAP4rev1 Server
- // consume the response before doing anything else.
- ImapResponse response = mParser.readResponse(false);
- if (!response.isOk()) {
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_INVALID_INITIAL_SERVER_RESPONSE);
- throw new MessagingException(
- MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
- "Invalid server initial response");
- }
-
- queryCapability();
-
- maybeDoStartTls();
-
- // LOGIN
- doLogin();
- } catch (SSLException e) {
- LogUtils.d(TAG, "SSLException ", e);
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_SSL_EXCEPTION);
- throw new CertificateValidationException(e.getMessage(), e);
- } catch (IOException ioe) {
- LogUtils.d(TAG, "IOException", ioe);
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_IOE_ON_OPEN);
- throw ioe;
- } finally {
- destroyResponses();
- }
- }
-
- void logout() {
- try {
- sendCommand(ImapConstants.LOGOUT, false);
- if (!mParser.readResponse(true).is(0, ImapConstants.BYE)) {
- VvmLog.e(TAG, "Server did not respond LOGOUT with BYE");
- }
- if (!mParser.readResponse(false).isOk()) {
- VvmLog.e(TAG, "Server did not respond OK after LOGOUT");
- }
- } catch (IOException | MessagingException e) {
- VvmLog.e(TAG, "Error while logging out:" + e);
- }
- }
-
- /**
- * Closes the connection and releases all resources. This connection can not be used again
- * until {@link #setStore(ImapStore)} is called.
- */
- void close() {
- if (mTransport != null) {
- logout();
- mTransport.close();
- mTransport = null;
- }
- destroyResponses();
- mParser = null;
- mImapStore = null;
- }
-
- /**
- * Attempts to convert the connection into secure connection.
- */
- private void maybeDoStartTls() throws IOException, MessagingException {
- // STARTTLS is required in the OMTP standard but not every implementation support it.
- // Make sure the server does have this capability
- if (hasCapability(ImapConstants.CAPABILITY_STARTTLS)) {
- executeSimpleCommand(ImapConstants.STARTTLS);
- mTransport.reopenTls();
- createParser();
- // The cached capabilities should be refreshed after TLS is established.
- queryCapability();
- }
- }
-
- /**
- * Logs into the IMAP server
- */
- private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
- try {
- if (mCapabilities.contains(ImapConstants.CAPABILITY_AUTH_DIGEST_MD5)) {
- doDigestMd5Auth();
- } else {
- executeSimpleCommand(getLoginPhrase(), true);
- }
- } catch (ImapException ie) {
- LogUtils.d(TAG, "ImapException", ie);
- String status = ie.getStatus();
- String statusMessage = ie.getStatusMessage();
- String alertText = ie.getAlertText();
-
- if (ImapConstants.NO.equals(status)) {
- switch (statusMessage) {
- case ImapConstants.NO_UNKNOWN_USER:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_USER);
- break;
- case ImapConstants.NO_UNKNOWN_CLIENT:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_DEVICE);
- break;
- case ImapConstants.NO_INVALID_PASSWORD:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_INVALID_PASSWORD);
- break;
- case ImapConstants.NO_MAILBOX_NOT_INITIALIZED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_MAILBOX_NOT_INITIALIZED);
- break;
- case ImapConstants.NO_SERVICE_IS_NOT_PROVISIONED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_PROVISIONED);
- break;
- case ImapConstants.NO_SERVICE_IS_NOT_ACTIVATED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_ACTIVATED);
- break;
- case ImapConstants.NO_USER_IS_BLOCKED:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_AUTH_USER_IS_BLOCKED);
- break;
- case ImapConstants.NO_APPLICATION_ERROR:
- mImapStore.getImapHelper()
- .handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
- default:
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
- }
- throw new AuthenticationFailedException(alertText, ie);
- }
-
- mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
- throw new MessagingException(alertText, ie);
- }
- }
-
- private void doDigestMd5Auth() throws IOException, MessagingException {
-
- // Initiate the authentication.
- // The server will issue us a challenge, asking to run MD5 on the nonce with our password
- // and other data, including the cnonce we randomly generated.
- //
- // C: a AUTHENTICATE DIGEST-MD5
- // S: (BASE64) realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
- // algorithm=md5-sess,charset=utf-8
- List<ImapResponse> responses = executeSimpleCommand(
- ImapConstants.AUTHENTICATE + " " + ImapConstants.AUTH_DIGEST_MD5);
- String decodedChallenge = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
-
- Map<String, String> challenge = DigestMd5Utils.parseDigestMessage(decodedChallenge);
- DigestMd5Utils.Data data = new DigestMd5Utils.Data(mImapStore, mTransport, challenge);
-
- String response = data.createResponse();
- // Respond to the challenge. If the server accepts it, it will reply a response-auth which
- // is the MD5 of our password and the cnonce we've provided, to prove the server does know
- // the password.
- //
- // C: (BASE64) charset=utf-8,username="chris",realm="elwood.innosoft.com",
- // nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
- // digest-uri="imap/elwood.innosoft.com",
- // response=d388dad90d4bbd760a152321f2143af7,qop=auth
- // S: (BASE64) rspauth=ea40f60335c427b5527b84dbabcdfffd
-
- responses = executeContinuationResponse(encodeBase64(response), true);
-
- // Verify response-auth.
- // If failed verifyResponseAuth() will throw a MessagingException, terminating the
- // connection
- String decodedResponseAuth = decodeBase64(responses.get(0).getStringOrEmpty(0).getString());
- data.verifyResponseAuth(decodedResponseAuth);
-
- // Send a empty response to indicate we've accepted the response-auth
- //
- // C: (empty)
- // S: a OK User logged in
- executeContinuationResponse("", false);
-
- }
-
- private static String decodeBase64(String string) {
- return new String(Base64.decode(string, Base64.DEFAULT));
- }
-
- private static String encodeBase64(String string) {
- return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
- }
-
- private void queryCapability() throws IOException, MessagingException {
- List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
- mCapabilities.clear();
- Set<String> disabledCapabilities = mImapStore.getImapHelper().getConfig()
- .getDisabledCapabilities();
- for (ImapResponse response : responses) {
- if (response.isTagged()) {
- continue;
- }
- for (int i = 0; i < response.size(); i++) {
- String capability = response.getStringOrEmpty(i).getString();
- if (disabledCapabilities != null) {
- if (!disabledCapabilities.contains(capability)) {
- mCapabilities.add(capability);
- }
- } else {
- mCapabilities.add(capability);
- }
- }
- }
-
- LogUtils.d(TAG, "Capabilities: " + mCapabilities.toString());
- }
-
- private boolean hasCapability(String capability) {
- return mCapabilities.contains(capability);
- }
- /**
- * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
- * set it to {@link #mParser}.
- *
- * If we already have an {@link ImapResponseParser}, we
- * {@link #destroyResponses()} and throw it away.
- */
- private void createParser() {
- destroyResponses();
- mParser = new ImapResponseParser(mTransport.getInputStream());
- }
-
-
- public void destroyResponses() {
- if (mParser != null) {
- mParser.destroyResponses();
- }
- }
-
- public ImapResponse readResponse() throws IOException, MessagingException {
- return mParser.readResponse(false);
- }
-
- public List<ImapResponse> executeSimpleCommand(String command)
- throws IOException, MessagingException{
- return executeSimpleCommand(command, false);
- }
-
- /**
- * Send a single command to the server. The command will be preceded by an IMAP command
- * tag and followed by \r\n (caller need not supply them).
- * Execute a simple command at the server, a simple command being one that is sent in a single
- * line of text
- *
- * @param command the command to send to the server
- * @param sensitive whether the command should be redacted in logs (used for login)
- * @return a list of ImapResponses
- * @throws IOException
- * @throws MessagingException
- */
- public List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
- throws IOException, MessagingException {
- // TODO: It may be nice to catch IOExceptions and close the connection here.
- // Currently, we expect callers to do that, but if they fail to we'll be in a broken state.
- sendCommand(command, sensitive);
- return getCommandResponses();
- }
-
- public String sendCommand(String command, boolean sensitive)
- throws IOException, MessagingException {
- open();
-
- if (mTransport == null) {
- throw new IOException("Null transport");
- }
- String tag = Integer.toString(mNextCommandTag.incrementAndGet());
- String commandToSend = tag + " " + command;
- mTransport.writeLine(commandToSend, (sensitive ? IMAP_REDACTED_LOG : command));
- return tag;
- }
-
- List<ImapResponse> executeContinuationResponse(String response, boolean sensitive)
- throws IOException, MessagingException {
- mTransport.writeLine(response, (sensitive ? IMAP_REDACTED_LOG : response));
- return getCommandResponses();
- }
-
- /**
- * Read and return all of the responses from the most recent command sent to the server
- *
- * @return a list of ImapResponses
- * @throws IOException
- * @throws MessagingException
- */
- List<ImapResponse> getCommandResponses()
- throws IOException, MessagingException {
- final List<ImapResponse> responses = new ArrayList<ImapResponse>();
- ImapResponse response;
- do {
- response = mParser.readResponse(false);
- responses.add(response);
- } while (!(response.isTagged() || response.isContinuationRequest()));
-
- if (!(response.isOk() || response.isContinuationRequest())) {
- final String toString = response.toString();
- final String status = response.getStatusOrEmpty().getString();
- final String statusMessage = response.getStatusResponseTextOrEmpty().getString();
- final String alert = response.getAlertTextOrEmpty().getString();
- final String responseCode = response.getResponseCodeOrEmpty().getString();
- destroyResponses();
- throw new ImapException(toString, status, statusMessage, alert, responseCode);
- }
- return responses;
- }
-}