Home | History | Annotate | Download | only in store
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.mail.store;
     18 
     19 import android.text.TextUtils;
     20 import android.util.Base64;
     21 
     22 import com.android.email.mail.internet.AuthenticationCache;
     23 import com.android.email.mail.store.ImapStore.ImapException;
     24 import com.android.email.mail.store.imap.ImapConstants;
     25 import com.android.email.mail.store.imap.ImapList;
     26 import com.android.email.mail.store.imap.ImapResponse;
     27 import com.android.email.mail.store.imap.ImapResponseParser;
     28 import com.android.email.mail.store.imap.ImapUtility;
     29 import com.android.email.mail.transport.DiscourseLogger;
     30 import com.android.email.mail.transport.MailTransport;
     31 import com.android.email2.ui.MailActivityEmail;
     32 import com.android.emailcommon.Logging;
     33 import com.android.emailcommon.mail.AuthenticationFailedException;
     34 import com.android.emailcommon.mail.CertificateValidationException;
     35 import com.android.emailcommon.mail.MessagingException;
     36 import com.android.mail.utils.LogUtils;
     37 
     38 import java.io.IOException;
     39 import java.util.ArrayList;
     40 import java.util.Collections;
     41 import java.util.List;
     42 import java.util.concurrent.atomic.AtomicInteger;
     43 
     44 import javax.net.ssl.SSLException;
     45 
     46 /**
     47  * A cacheable class that stores the details for a single IMAP connection.
     48  */
     49 class ImapConnection {
     50     // Always check in FALSE
     51     private static final boolean DEBUG_FORCE_SEND_ID = false;
     52 
     53     /** ID capability per RFC 2971*/
     54     public static final int CAPABILITY_ID        = 1 << 0;
     55     /** NAMESPACE capability per RFC 2342 */
     56     public static final int CAPABILITY_NAMESPACE = 1 << 1;
     57     /** STARTTLS capability per RFC 3501 */
     58     public static final int CAPABILITY_STARTTLS  = 1 << 2;
     59     /** UIDPLUS capability per RFC 4315 */
     60     public static final int CAPABILITY_UIDPLUS   = 1 << 3;
     61 
     62     /** The capabilities supported; a set of CAPABILITY_* values. */
     63     private int mCapabilities;
     64     static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
     65     MailTransport mTransport;
     66     private ImapResponseParser mParser;
     67     private ImapStore mImapStore;
     68     private String mLoginPhrase;
     69     private String mAccessToken;
     70     private String mIdPhrase = null;
     71 
     72     /** # of command/response lines to log upon crash. */
     73     private static final int DISCOURSE_LOGGER_SIZE = 64;
     74     private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
     75     /**
     76      * Next tag to use.  All connections associated to the same ImapStore instance share the same
     77      * counter to make tests simpler.
     78      * (Some of the tests involve multiple connections but only have a single counter to track the
     79      * tag.)
     80      */
     81     private final AtomicInteger mNextCommandTag = new AtomicInteger(0);
     82 
     83     // Keep others from instantiating directly
     84     ImapConnection(ImapStore store) {
     85         setStore(store);
     86     }
     87 
     88     void setStore(ImapStore store) {
     89         // TODO: maybe we should throw an exception if the connection is not closed here,
     90         // if it's not currently closed, then we won't reopen it, so if the credentials have
     91         // changed, the connection will not be reestablished.
     92         mImapStore = store;
     93         mLoginPhrase = null;
     94     }
     95 
     96     /**
     97      * Generates and returns the phrase to be used for authentication. This will be a LOGIN with
     98      * username and password, or an OAUTH authentication string, with username and access token.
     99      * Currently, these are the only two auth mechanisms supported.
    100      *
    101      * @throws IOException
    102      * @throws AuthenticationFailedException
    103      * @return the login command string to sent to the IMAP server
    104      */
    105     String getLoginPhrase() throws MessagingException, IOException {
    106         // build the LOGIN string once (instead of over-and-over again.)
    107         if (mImapStore.getUseOAuth()) {
    108             // We'll recreate the login phrase if it's null, or if the access token
    109             // has changed.
    110             final String accessToken = AuthenticationCache.getInstance().retrieveAccessToken(
    111                     mImapStore.getContext(), mImapStore.getAccount());
    112             if (mLoginPhrase == null || !TextUtils.equals(mAccessToken, accessToken)) {
    113                 mAccessToken = accessToken;
    114                 final String oauthCode = "user=" + mImapStore.getUsername() + '\001' +
    115                         "auth=Bearer " + mAccessToken + '\001' + '\001';
    116                 mLoginPhrase = ImapConstants.AUTHENTICATE + " " + ImapConstants.XOAUTH2 + " " +
    117                         Base64.encodeToString(oauthCode.getBytes(), Base64.NO_WRAP);
    118             }
    119         } else {
    120             if (mLoginPhrase == null) {
    121                 if (mImapStore.getUsername() != null && mImapStore.getPassword() != null) {
    122                     // build the LOGIN string once (instead of over-and-over again.)
    123                     // apply the quoting here around the built-up password
    124                     mLoginPhrase = ImapConstants.LOGIN + " " + mImapStore.getUsername() + " "
    125                             + ImapUtility.imapQuoted(mImapStore.getPassword());
    126                 }
    127             }
    128         }
    129         return mLoginPhrase;
    130     }
    131 
    132     void open() throws IOException, MessagingException {
    133         if (mTransport != null && mTransport.isOpen()) {
    134             return;
    135         }
    136 
    137         try {
    138             // copy configuration into a clean transport, if necessary
    139             if (mTransport == null) {
    140                 mTransport = mImapStore.cloneTransport();
    141             }
    142 
    143             mTransport.open();
    144 
    145             createParser();
    146 
    147             // BANNER
    148             mParser.readResponse();
    149 
    150             // CAPABILITY
    151             ImapResponse capabilities = queryCapabilities();
    152 
    153             boolean hasStartTlsCapability =
    154                 capabilities.contains(ImapConstants.STARTTLS);
    155 
    156             // TLS
    157             ImapResponse newCapabilities = doStartTls(hasStartTlsCapability);
    158             if (newCapabilities != null) {
    159                 capabilities = newCapabilities;
    160             }
    161 
    162             // NOTE: An IMAP response MUST be processed before issuing any new IMAP
    163             // requests. Subsequent requests may destroy previous response data. As
    164             // such, we save away capability information here for future use.
    165             setCapabilities(capabilities);
    166             String capabilityString = capabilities.flatten();
    167 
    168             // ID
    169             doSendId(isCapable(CAPABILITY_ID), capabilityString);
    170 
    171             // LOGIN
    172             doLogin();
    173 
    174             // NAMESPACE (only valid in the Authenticated state)
    175             doGetNamespace(isCapable(CAPABILITY_NAMESPACE));
    176 
    177             // Gets the path separator from the server
    178             doGetPathSeparator();
    179 
    180             mImapStore.ensurePrefixIsValid();
    181         } catch (SSLException e) {
    182             if (MailActivityEmail.DEBUG) {
    183                 LogUtils.d(Logging.LOG_TAG, e, "SSLException");
    184             }
    185             throw new CertificateValidationException(e.getMessage(), e);
    186         } catch (IOException ioe) {
    187             // NOTE:  Unlike similar code in POP3, I'm going to rethrow as-is.  There is a lot
    188             // of other code here that catches IOException and I don't want to break it.
    189             // This catch is only here to enhance logging of connection-time issues.
    190             if (MailActivityEmail.DEBUG) {
    191                 LogUtils.d(Logging.LOG_TAG, ioe, "IOException");
    192             }
    193             throw ioe;
    194         } finally {
    195             destroyResponses();
    196         }
    197     }
    198 
    199     /**
    200      * Closes the connection and releases all resources. This connection can not be used again
    201      * until {@link #setStore(ImapStore)} is called.
    202      */
    203     void close() {
    204         if (mTransport != null) {
    205             mTransport.close();
    206             mTransport = null;
    207         }
    208         destroyResponses();
    209         mParser = null;
    210         mImapStore = null;
    211     }
    212 
    213     /**
    214      * Returns whether or not the specified capability is supported by the server.
    215      */
    216     private boolean isCapable(int capability) {
    217         return (mCapabilities & capability) != 0;
    218     }
    219 
    220     /**
    221      * Sets the capability flags according to the response provided by the server.
    222      * Note: We only set the capability flags that we are interested in. There are many IMAP
    223      * capabilities that we do not track.
    224      */
    225     private void setCapabilities(ImapResponse capabilities) {
    226         if (capabilities.contains(ImapConstants.ID)) {
    227             mCapabilities |= CAPABILITY_ID;
    228         }
    229         if (capabilities.contains(ImapConstants.NAMESPACE)) {
    230             mCapabilities |= CAPABILITY_NAMESPACE;
    231         }
    232         if (capabilities.contains(ImapConstants.UIDPLUS)) {
    233             mCapabilities |= CAPABILITY_UIDPLUS;
    234         }
    235         if (capabilities.contains(ImapConstants.STARTTLS)) {
    236             mCapabilities |= CAPABILITY_STARTTLS;
    237         }
    238     }
    239 
    240     /**
    241      * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
    242      * set it to {@link #mParser}.
    243      *
    244      * If we already have an {@link ImapResponseParser}, we
    245      * {@link #destroyResponses()} and throw it away.
    246      */
    247     private void createParser() {
    248         destroyResponses();
    249         mParser = new ImapResponseParser(mTransport.getInputStream(), mDiscourse);
    250     }
    251 
    252     void destroyResponses() {
    253         if (mParser != null) {
    254             mParser.destroyResponses();
    255         }
    256     }
    257 
    258     boolean isTransportOpenForTest() {
    259         return mTransport != null && mTransport.isOpen();
    260     }
    261 
    262     ImapResponse readResponse() throws IOException, MessagingException {
    263         return mParser.readResponse();
    264     }
    265 
    266     /**
    267      * Send a single command to the server.  The command will be preceded by an IMAP command
    268      * tag and followed by \r\n (caller need not supply them).
    269      *
    270      * @param command The command to send to the server
    271      * @param sensitive If true, the command will not be logged
    272      * @return Returns the command tag that was sent
    273      */
    274     String sendCommand(String command, boolean sensitive)
    275             throws MessagingException, IOException {
    276         LogUtils.d(Logging.LOG_TAG, "sendCommand %s", (sensitive ? IMAP_REDACTED_LOG : command));
    277         open();
    278         return sendCommandInternal(command, sensitive);
    279     }
    280 
    281     String sendCommandInternal(String command, boolean sensitive)
    282             throws MessagingException, IOException {
    283         if (mTransport == null) {
    284             throw new IOException("Null transport");
    285         }
    286         String tag = Integer.toString(mNextCommandTag.incrementAndGet());
    287         String commandToSend = tag + " " + command;
    288         mTransport.writeLine(commandToSend, sensitive ? IMAP_REDACTED_LOG : null);
    289         mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
    290         return tag;
    291     }
    292 
    293     /**
    294      * Send a single, complex command to the server.  The command will be preceded by an IMAP
    295      * command tag and followed by \r\n (caller need not supply them).  After each piece of the
    296      * command, a response will be read which MUST be a continuation request.
    297      *
    298      * @param commands An array of Strings comprising the command to be sent to the server
    299      * @return Returns the command tag that was sent
    300      */
    301     String sendComplexCommand(List<String> commands, boolean sensitive) throws MessagingException,
    302             IOException {
    303         open();
    304         String tag = Integer.toString(mNextCommandTag.incrementAndGet());
    305         int len = commands.size();
    306         for (int i = 0; i < len; i++) {
    307             String commandToSend = commands.get(i);
    308             // The first part of the command gets the tag
    309             if (i == 0) {
    310                 commandToSend = tag + " " + commandToSend;
    311             } else {
    312                 // Otherwise, read the response from the previous part of the command
    313                 ImapResponse response = readResponse();
    314                 // If it isn't a continuation request, that's an error
    315                 if (!response.isContinuationRequest()) {
    316                     throw new MessagingException("Expected continuation request");
    317                 }
    318             }
    319             // Send the command
    320             mTransport.writeLine(commandToSend, null);
    321             mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
    322         }
    323         return tag;
    324     }
    325 
    326     List<ImapResponse> executeSimpleCommand(String command) throws IOException, MessagingException {
    327         return executeSimpleCommand(command, false);
    328     }
    329 
    330     /**
    331      * Read and return all of the responses from the most recent command sent to the server
    332      *
    333      * @return a list of ImapResponses
    334      * @throws IOException
    335      * @throws MessagingException
    336      */
    337     List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
    338         final List<ImapResponse> responses = new ArrayList<ImapResponse>();
    339         ImapResponse response;
    340         do {
    341             response = mParser.readResponse();
    342             responses.add(response);
    343         } while (!response.isTagged());
    344 
    345         if (!response.isOk()) {
    346             final String toString = response.toString();
    347             final String alert = response.getAlertTextOrEmpty().getString();
    348             final String responseCode = response.getResponseCodeOrEmpty().getString();
    349             destroyResponses();
    350 
    351             // if the response code indicates an error occurred within the server, indicate that
    352             if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
    353                 throw new MessagingException(MessagingException.SERVER_ERROR, alert);
    354             }
    355 
    356             throw new ImapException(toString, alert, responseCode);
    357         }
    358         return responses;
    359     }
    360 
    361     /**
    362      * Execute a simple command at the server, a simple command being one that is sent in a single
    363      * line of text
    364      *
    365      * @param command the command to send to the server
    366      * @param sensitive whether the command should be redacted in logs (used for login)
    367      * @return a list of ImapResponses
    368      * @throws IOException
    369      * @throws MessagingException
    370      */
    371      List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
    372             throws IOException, MessagingException {
    373          // TODO: It may be nice to catch IOExceptions and close the connection here.
    374          // Currently, we expect callers to do that, but if they fail to we'll be in a broken state.
    375          sendCommand(command, sensitive);
    376          return getCommandResponses();
    377     }
    378 
    379      /**
    380       * Execute a complex command at the server, a complex command being one that must be sent in
    381       * multiple lines due to the use of string literals
    382       *
    383       * @param commands a list of strings that comprise the command to be sent to the server
    384       * @param sensitive whether the command should be redacted in logs (used for login)
    385       * @return a list of ImapResponses
    386       * @throws IOException
    387       * @throws MessagingException
    388       */
    389       List<ImapResponse> executeComplexCommand(List<String> commands, boolean sensitive)
    390             throws IOException, MessagingException {
    391           sendComplexCommand(commands, sensitive);
    392           return getCommandResponses();
    393       }
    394 
    395     /**
    396      * Query server for capabilities.
    397      */
    398     private ImapResponse queryCapabilities() throws IOException, MessagingException {
    399         ImapResponse capabilityResponse = null;
    400         for (ImapResponse r : executeSimpleCommand(ImapConstants.CAPABILITY)) {
    401             if (r.is(0, ImapConstants.CAPABILITY)) {
    402                 capabilityResponse = r;
    403                 break;
    404             }
    405         }
    406         if (capabilityResponse == null) {
    407             throw new MessagingException("Invalid CAPABILITY response received");
    408         }
    409         return capabilityResponse;
    410     }
    411 
    412     /**
    413      * Sends client identification information to the IMAP server per RFC 2971. If
    414      * the server does not support the ID command, this will perform no operation.
    415      *
    416      * Interoperability hack:  Never send ID to *.secureserver.net, which sends back a
    417      * malformed response that our parser can't deal with.
    418      */
    419     private void doSendId(boolean hasIdCapability, String capabilities)
    420             throws MessagingException {
    421         if (!hasIdCapability) return;
    422 
    423         // Never send ID to *.secureserver.net
    424         String host = mTransport.getHost();
    425         if (host.toLowerCase().endsWith(".secureserver.net")) return;
    426 
    427         // Assign user-agent string (for RFC2971 ID command)
    428         String mUserAgent =
    429                 ImapStore.getImapId(mImapStore.getContext(), mImapStore.getUsername(), host,
    430                         capabilities);
    431 
    432         if (mUserAgent != null) {
    433             mIdPhrase = ImapConstants.ID + " (" + mUserAgent + ")";
    434         } else if (DEBUG_FORCE_SEND_ID) {
    435             mIdPhrase = ImapConstants.ID + " " + ImapConstants.NIL;
    436         }
    437         // else: mIdPhrase = null, no ID will be emitted
    438 
    439         // Send user-agent in an RFC2971 ID command
    440         if (mIdPhrase != null) {
    441             try {
    442                 executeSimpleCommand(mIdPhrase);
    443             } catch (ImapException ie) {
    444                 // Log for debugging, but this is not a fatal problem.
    445                 if (MailActivityEmail.DEBUG) {
    446                     LogUtils.d(Logging.LOG_TAG, ie, "ImapException");
    447                 }
    448             } catch (IOException ioe) {
    449                 // Special case to handle malformed OK responses and ignore them.
    450                 // A true IOException will recur on the following login steps
    451                 // This can go away after the parser is fixed - see bug 2138981
    452             }
    453         }
    454     }
    455 
    456     /**
    457      * Gets the user's Personal Namespace from the IMAP server per RFC 2342. If the user
    458      * explicitly sets a namespace (using setup UI) or if the server does not support the
    459      * namespace command, this will perform no operation.
    460      */
    461     private void doGetNamespace(boolean hasNamespaceCapability) throws MessagingException {
    462         // user did not specify a hard-coded prefix; try to get it from the server
    463         if (hasNamespaceCapability && !mImapStore.isUserPrefixSet()) {
    464             List<ImapResponse> responseList = Collections.emptyList();
    465 
    466             try {
    467                 responseList = executeSimpleCommand(ImapConstants.NAMESPACE);
    468             } catch (ImapException ie) {
    469                 // Log for debugging, but this is not a fatal problem.
    470                 if (MailActivityEmail.DEBUG) {
    471                     LogUtils.d(Logging.LOG_TAG, ie, "ImapException");
    472                 }
    473             } catch (IOException ioe) {
    474                 // Special case to handle malformed OK responses and ignore them.
    475             }
    476 
    477             for (ImapResponse response: responseList) {
    478                 if (response.isDataResponse(0, ImapConstants.NAMESPACE)) {
    479                     ImapList namespaceList = response.getListOrEmpty(1);
    480                     ImapList namespace = namespaceList.getListOrEmpty(0);
    481                     String namespaceString = namespace.getStringOrEmpty(0).getString();
    482                     if (!TextUtils.isEmpty(namespaceString)) {
    483                         mImapStore.setPathPrefix(ImapStore.decodeFolderName(namespaceString, null));
    484                         mImapStore.setPathSeparator(namespace.getStringOrEmpty(1).getString());
    485                     }
    486                 }
    487             }
    488         }
    489     }
    490 
    491     /**
    492      * Logs into the IMAP server
    493      */
    494     private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
    495         try {
    496             if (mImapStore.getUseOAuth()) {
    497                 // SASL authentication can take multiple steps. Currently the only SASL
    498                 // authentication supported is OAuth.
    499                 doSASLAuth();
    500             } else {
    501                 executeSimpleCommand(getLoginPhrase(), true);
    502             }
    503         } catch (ImapException ie) {
    504             if (MailActivityEmail.DEBUG) {
    505                 LogUtils.d(Logging.LOG_TAG, ie, "ImapException");
    506             }
    507 
    508             final String code = ie.getResponseCode();
    509             final String alertText = ie.getAlertText();
    510 
    511             // if the response code indicates expired or bad credentials, throw a special exception
    512             if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
    513                     ImapConstants.EXPIRED.equals(code)) {
    514                 throw new AuthenticationFailedException(alertText, ie);
    515             }
    516 
    517             throw new MessagingException(alertText, ie);
    518         }
    519     }
    520 
    521     /**
    522      * Performs an SASL authentication. Currently, the only type of SASL authentication supported
    523      * is OAuth.
    524      * @throws MessagingException
    525      * @throws IOException
    526      */
    527     private void doSASLAuth() throws MessagingException, IOException {
    528         LogUtils.d(Logging.LOG_TAG, "doSASLAuth");
    529         ImapResponse response = getOAuthResponse();
    530         if (!response.isOk()) {
    531             // Failed to authenticate. This may be just due to an expired token.
    532             LogUtils.d(Logging.LOG_TAG, "failed to authenticate, retrying");
    533             destroyResponses();
    534             // Clear the login phrase, this will force us to refresh the auth token.
    535             mLoginPhrase = null;
    536             // Close the transport so that we'll retry the authentication.
    537             if (mTransport != null) {
    538                 mTransport.close();
    539                 mTransport = null;
    540             }
    541             response = getOAuthResponse();
    542             if (!response.isOk()) {
    543                 LogUtils.d(Logging.LOG_TAG, "failed to authenticate, giving up");
    544                 destroyResponses();
    545                 throw new AuthenticationFailedException("OAuth failed after refresh");
    546             }
    547         }
    548     }
    549 
    550     private ImapResponse getOAuthResponse() throws IOException, MessagingException {
    551         ImapResponse response;
    552         sendCommandInternal(getLoginPhrase(), true);
    553         do {
    554             response = mParser.readResponse();
    555         } while (!response.isTagged() && !response.isContinuationRequest());
    556 
    557         if (response.isContinuationRequest()) {
    558             // SASL allows for a challenge/response type authentication, so if it doesn't yet have
    559             // enough info, it will send back a continuation request.
    560             // Currently, the only type of authentication we support is OAuth. The only case where
    561             // it will send a continuation request is when we fail to authenticate. We need to
    562             // reply with a CR/LF, and it will then return with a NO response.
    563             sendCommandInternal("", true);
    564             response = readResponse();
    565         }
    566 
    567         // if the response code indicates an error occurred within the server, indicate that
    568         final String responseCode = response.getResponseCodeOrEmpty().getString();
    569         if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
    570             final String alert = response.getAlertTextOrEmpty().getString();
    571             throw new MessagingException(MessagingException.SERVER_ERROR, alert);
    572         }
    573 
    574         return response;
    575     }
    576 
    577     /**
    578      * Gets the path separator per the LIST command in RFC 3501. If the path separator
    579      * was obtained while obtaining the namespace or there is no prefix defined, this
    580      * will perform no operation.
    581      */
    582     private void doGetPathSeparator() throws MessagingException {
    583         // user did not specify a hard-coded prefix; try to get it from the server
    584         if (mImapStore.isUserPrefixSet()) {
    585             List<ImapResponse> responseList = Collections.emptyList();
    586 
    587             try {
    588                 responseList = executeSimpleCommand(ImapConstants.LIST + " \"\" \"\"");
    589             } catch (ImapException ie) {
    590                 // Log for debugging, but this is not a fatal problem.
    591                 if (MailActivityEmail.DEBUG) {
    592                     LogUtils.d(Logging.LOG_TAG, ie, "ImapException");
    593                 }
    594             } catch (IOException ioe) {
    595                 // Special case to handle malformed OK responses and ignore them.
    596             }
    597 
    598             for (ImapResponse response: responseList) {
    599                 if (response.isDataResponse(0, ImapConstants.LIST)) {
    600                     mImapStore.setPathSeparator(response.getStringOrEmpty(2).getString());
    601                 }
    602             }
    603         }
    604     }
    605 
    606     /**
    607      * Starts a TLS session with the IMAP server per RFC 3501. If the user has not opted
    608      * to use TLS or the server does not support the TLS capability, this will perform
    609      * no operation.
    610      */
    611     private ImapResponse doStartTls(boolean hasStartTlsCapability)
    612             throws IOException, MessagingException {
    613         if (mTransport.canTryTlsSecurity()) {
    614             if (hasStartTlsCapability) {
    615                 // STARTTLS
    616                 executeSimpleCommand(ImapConstants.STARTTLS);
    617 
    618                 mTransport.reopenTls();
    619                 createParser();
    620                 // Per RFC requirement (3501-6.2.1) gather new capabilities
    621                 return(queryCapabilities());
    622             } else {
    623                 if (MailActivityEmail.DEBUG) {
    624                     LogUtils.d(Logging.LOG_TAG, "TLS not supported but required");
    625                 }
    626                 throw new MessagingException(MessagingException.TLS_REQUIRED);
    627             }
    628         }
    629         return null;
    630     }
    631 
    632     /** @see DiscourseLogger#logLastDiscourse() */
    633     void logLastDiscourse() {
    634         mDiscourse.logLastDiscourse();
    635     }
    636 }
    637