196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License.
696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at
796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and
1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License.
1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email.mail.transport;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starlingimport android.content.Context;
20ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starlingimport android.util.Base64;
21ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling
2251c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdonimport com.android.email.DebugUtils;
2396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport com.android.email.mail.Sender;
24e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.email.mail.internet.AuthenticationCache;
2531d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
2631d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.internet.Rfc822Output;
272193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.Address;
282193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.AuthenticationFailedException;
292193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.CertificateValidationException;
302193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
31f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blankimport com.android.emailcommon.provider.Account;
32e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdonimport com.android.emailcommon.provider.Credential;
33a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
3412b82d9374947c9268217f45befe8a74bd9b60d7Ben Komaloimport com.android.emailcommon.provider.HostAuth;
35a8b683cf3f2efe726220c0235368cf6ea899e3baMarc Blankimport com.android.emailcommon.utility.EOLConvertingOutputStream;
36560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
3896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
39ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starlingimport java.net.Inet6Address;
4096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.InetAddress;
4196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
4296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport javax.net.ssl.SSLException;
4396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
4496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/**
4596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * This class handles all of the protocol-level aspects of sending messages via SMTP.
4696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
4796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class SmtpSender extends Sender {
482d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank
49b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private final Context mContext;
50b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank    private MailTransport mTransport;
51e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    private Account mAccount;
52b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private String mUsername;
53b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private String mPassword;
54e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    private boolean mUseOAuth;
5596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
5696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
57ae8ca3fbd1545c3a94011d7d70bcadac99e7779fAndy Stadler     * Static named constructor.
58ae8ca3fbd1545c3a94011d7d70bcadac99e7779fAndy Stadler     */
59daf869cf60de75bc91ed3aef6ac0bff1fe371733Todd Kennedy    public static Sender newInstance(Account account, Context context) throws MessagingException {
60daf869cf60de75bc91ed3aef6ac0bff1fe371733Todd Kennedy        return new SmtpSender(context, account);
61ae8ca3fbd1545c3a94011d7d70bcadac99e7779fAndy Stadler    }
62ae8ca3fbd1545c3a94011d7d70bcadac99e7779fAndy Stadler
63ae8ca3fbd1545c3a94011d7d70bcadac99e7779fAndy Stadler    /**
64daf869cf60de75bc91ed3aef6ac0bff1fe371733Todd Kennedy     * Creates a new sender for the given account.
6596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
66787c534f286f0267edb1a083ef191fac804060b5Marc Blank    public SmtpSender(Context context, Account account) {
67c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        mContext = context;
68e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        mAccount = account;
69daf869cf60de75bc91ed3aef6ac0bff1fe371733Todd Kennedy        HostAuth sendAuth = account.getOrCreateHostAuthSend(context);
707d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        mTransport = new MailTransport(context, "SMTP", sendAuth);
71daf869cf60de75bc91ed3aef6ac0bff1fe371733Todd Kennedy        String[] userInfoParts = sendAuth.getLogin();
7276472ae40cd55d17edb0420e8fc2a7bae60c50deTony Mantler        mUsername = userInfoParts[0];
7376472ae40cd55d17edb0420e8fc2a7bae60c50deTony Mantler        mPassword = userInfoParts[1];
74e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        Credential cred = sendAuth.getCredential(context);
75e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        if (cred != null) {
76e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            mUseOAuth = true;
77e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        }
7896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
7996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
8196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * For testing only.  Injects a different transport.  The transport should already be set
8296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * up and ready to use.  Do not use for real code.
8396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param testTransport The Transport to inject and use for all future communication.
8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
8517d3a29c9d8f7a27c463239f190bdcc4e0804527Jerry Xie    public void setTransport(MailTransport testTransport) {
8696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mTransport = testTransport;
8796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
89c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    @Override
9096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void open() throws MessagingException {
9196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mTransport.open();
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // Eat the banner
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            executeSimpleCommand(null);
9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
9796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            String localHost = "localhost";
98ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling            // Try to get local address in the proper format.
99f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            InetAddress localAddress = mTransport.getLocalAddress();
100f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            if (localAddress != null) {
101ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                // Address Literal formatted in accordance to RFC2821 Sec. 4.1.3
102ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                StringBuilder sb = new StringBuilder();
103ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                sb.append('[');
104ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                if (localAddress instanceof Inet6Address) {
105ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                    sb.append("IPv6:");
106ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                }
107ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                sb.append(localAddress.getHostAddress());
108ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                sb.append(']');
109ea11bfd6fcc73cffc1f2ef314133513e5291e6dbJon Starling                localHost = sb.toString();
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            String result = executeSimpleCommand("EHLO " + localHost);
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            /*
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * TODO may need to add code to fall back to HELO I switched it from
11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * using HELO on non STARTTLS connections because of AOL's mail
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * server. It won't let you use AUTH without EHLO.
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * We should really be paying more attention to the capabilities
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * and only attempting auth if it's available, and warning the user
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * if not.
12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             */
12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (mTransport.canTryTlsSecurity()) {
122c31e2555bf84a1f923fa98359ebc08447bf6905bMisha Nasledov                if (result.contains("STARTTLS")) {
12396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    executeSimpleCommand("STARTTLS");
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    mTransport.reopenTls();
12596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    /*
12696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                     * Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically,
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                     * Exim.
12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                     */
12996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    result = executeSimpleCommand("EHLO " + localHost);
130e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler                } else {
13151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon                    if (DebugUtils.DEBUG) {
132560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                        LogUtils.d(Logging.LOG_TAG, "TLS not supported but required");
13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    }
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    throw new MessagingException(MessagingException.TLS_REQUIRED);
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
13796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            /*
13996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             * result contains the results of the EHLO in concatenated form
14096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project             */
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            boolean authLoginSupported = result.matches(".*AUTH.*LOGIN.*$");
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            boolean authPlainSupported = result.matches(".*AUTH.*PLAIN.*$");
143e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            boolean authOAuthSupported = result.matches(".*AUTH.*XOAUTH2.*$");
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
145e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            if (mUseOAuth) {
146e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                if (!authOAuthSupported) {
147e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                    LogUtils.w(Logging.LOG_TAG, "OAuth requested, but not supported.");
148e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                    throw new MessagingException(MessagingException.OAUTH_NOT_SUPPORTED);
149e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                }
150e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                saslAuthOAuth(mUsername);
151e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            } else if (mUsername != null && mUsername.length() > 0 && mPassword != null
15296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    && mPassword.length() > 0) {
15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                if (authPlainSupported) {
15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    saslAuthPlain(mUsername, mPassword);
15596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
15696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else if (authLoginSupported) {
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    saslAuthLogin(mUsername, mPassword);
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
15996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                else {
160e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                    LogUtils.w(Logging.LOG_TAG, "No valid authentication mechanism found.");
16196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    throw new MessagingException(MessagingException.AUTH_REQUIRED);
16296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                }
163e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            } else {
16481f9a57a655fe842ddd86b14afe75bcd575edb81Martin Hibdon                // It is acceptable to hvae no authentication at all for SMTP.
16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (SSLException e) {
16751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
168560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, e.toString());
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new CertificateValidationException(e.getMessage(), e);
17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
17251c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
173560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ioe.toString());
17496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
17596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
17696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
17796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
17896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
179c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    @Override
180c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    public void sendMessage(long messageId) throws MessagingException {
18196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        close();
18296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        open();
183c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler
184c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        Message message = Message.restoreMessageWithId(mContext, messageId);
185c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        if (message == null) {
186c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            throw new MessagingException("Trying to send non-existent message id="
187c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler                    + Long.toString(messageId));
188c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler        }
1891fa303478c61e0d703011996e358037eef523176James Lemieux        Address from = Address.firstAddress(message.mFrom);
1901fa303478c61e0d703011996e358037eef523176James Lemieux        Address[] to = Address.fromHeader(message.mTo);
1911fa303478c61e0d703011996e358037eef523176James Lemieux        Address[] cc = Address.fromHeader(message.mCc);
1921fa303478c61e0d703011996e358037eef523176James Lemieux        Address[] bcc = Address.fromHeader(message.mBcc);
19396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
1954fd97a305065dfed11adf50612e55b9647ceb236Jack Bates            executeSimpleCommand("MAIL FROM:" + "<" + from.getAddress() + ">");
196c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            for (Address address : to) {
1974fd97a305065dfed11adf50612e55b9647ceb236Jack Bates                executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
19896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
199c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            for (Address address : cc) {
2004fd97a305065dfed11adf50612e55b9647ceb236Jack Bates                executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
20196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
202c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler            for (Address address : bcc) {
2034fd97a305065dfed11adf50612e55b9647ceb236Jack Bates                executeSimpleCommand("RCPT TO:" + "<" + address.getAddress().trim() + ">");
20496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            executeSimpleCommand("DATA");
20696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // TODO byte stuffing
2077c3f85885c767b58eaa4dbbc8e8339bd567b73ebYu Ping Hu            Rfc822Output.writeTo(mContext, message,
2080d49ef78ebc1b0d65c31241f5b38f95397eebe34Todd Kennedy                    new EOLConvertingOutputStream(mTransport.getOutputStream()),
2090d49ef78ebc1b0d65c31241f5b38f95397eebe34Todd Kennedy                    false /* do not use smart reply */,
2107c3f85885c767b58eaa4dbbc8e8339bd567b73ebYu Ping Hu                    false /* do not send BCC */,
2117c3f85885c767b58eaa4dbbc8e8339bd567b73ebYu Ping Hu                    null  /* attachments are in the message itself */);
21296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            executeSimpleCommand("\r\n.");
21396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException("Unable to send message", ioe);
21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
21696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
21796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
21896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
2192d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank     * Close the protocol (and the transport below it).
2202d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank     *
22196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * MUST NOT return any exceptions.
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
223c640cbbaf385566e1b6de361b2b23156e10f695dAndrew Stadler    @Override
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void close() {
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mTransport.close();
22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Send a single command and wait for a single response.  Handles responses that continue
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * onto multiple lines.  Throws MessagingException if response code is 4xx or 5xx.  All traffic
23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * is logged (if debug logging is enabled) so do not use this function for user ID or password.
2322d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank     *
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param command The command string to send to the server.
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return Returns the response string from the server.
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private String executeSimpleCommand(String command) throws IOException, MessagingException {
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return executeSensitiveCommand(command, null);
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
2392d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Send a single command and wait for a single response.  Handles responses that continue
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * onto multiple lines.  Throws MessagingException if response code is 4xx or 5xx.
2432d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank     *
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param command The command string to send to the server.
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param sensitiveReplacement If the command includes sensitive data (e.g. authentication)
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * please pass a replacement string here (for logging).
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @return Returns the response string from the server.
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
2492d5691cac1874eb3491353ab608a84c2a75e2b62Marc Blank    private String executeSensitiveCommand(String command, String sensitiveReplacement)
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throws IOException, MessagingException {
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        if (command != null) {
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mTransport.writeLine(command, sensitiveReplacement);
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
255b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank        String line = mTransport.readLine(true);
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String result = line;
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while (line.length() >= 4 && line.charAt(3) == '-') {
260b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank            line = mTransport.readLine(true);
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            result += line.substring(3);
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
264b011a812e05902d7e417b8c47acc90a592f4b412Andrew Stadler        if (result.length() > 0) {
265b011a812e05902d7e417b8c47acc90a592f4b412Andrew Stadler            char c = result.charAt(0);
266b011a812e05902d7e417b8c47acc90a592f4b412Andrew Stadler            if ((c == '4') || (c == '5')) {
267b011a812e05902d7e417b8c47acc90a592f4b412Andrew Stadler                throw new MessagingException(result);
268b011a812e05902d7e417b8c47acc90a592f4b412Andrew Stadler            }
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return result;
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: AUTH LOGIN
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 334 VXNlcm5hbWU6
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: d2VsZG9u
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 334 UGFzc3dvcmQ6
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: dzNsZDBu
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 235 2.0.0 OK Authenticated
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    Lines 2-5 of the conversation contain base64-encoded information. The same conversation, with base64 strings decoded, reads:
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//
28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: AUTH LOGIN
28696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 334 Username:
28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: weldon
28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 334 Password:
28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    C: w3ld0n
29096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project//    S: 235 2.0.0 OK Authenticated
29196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
29296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private void saslAuthLogin(String username, String password) throws MessagingException,
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        AuthenticationFailedException, IOException {
29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
29596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            executeSimpleCommand("AUTH LOGIN");
296f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker            executeSensitiveCommand(
297f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker                    Base64.encodeToString(username.getBytes(), Base64.NO_WRAP),
29896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    "/username redacted/");
299f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker            executeSensitiveCommand(
300f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker                    Base64.encodeToString(password.getBytes(), Base64.NO_WRAP),
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    "/password redacted/");
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (MessagingException me) {
30496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
30596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                throw new AuthenticationFailedException(me.getMessage());
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw me;
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private void saslAuthPlain(String username, String password) throws MessagingException,
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            AuthenticationFailedException, IOException {
31396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        byte[] data = ("\000" + username + "\000" + password).getBytes();
314f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker        data = Base64.encode(data, Base64.NO_WRAP);
31596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            executeSensitiveCommand("AUTH PLAIN " + new String(data), "AUTH PLAIN /redacted/");
31796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        catch (MessagingException me) {
31996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
32096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                throw new AuthenticationFailedException(me.getMessage());
32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw me;
32396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
32496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
325e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
326e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    private void saslAuthOAuth(String username) throws MessagingException,
327e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            AuthenticationFailedException, IOException {
328e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        final AuthenticationCache cache = AuthenticationCache.getInstance();
329e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        String accessToken = cache.retrieveAccessToken(mContext, mAccount);
330e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        try {
331e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            saslAuthOAuth(username, accessToken);
332e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        } catch (AuthenticationFailedException e) {
333e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            accessToken = cache.refreshAccessToken(mContext, mAccount);
334e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            saslAuthOAuth(username, accessToken);
335e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        }
336e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    }
337e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon
338e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    private void saslAuthOAuth(final String username, final String accessToken) throws IOException,
339e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            MessagingException {
340e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        final String authPhrase = "user=" + username + '\001' + "auth=Bearer " + accessToken +
341e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                '\001' + '\001';
342e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        byte[] data = Base64.encode(authPhrase.getBytes(), Base64.NO_WRAP);
343e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        try {
344e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            executeSensitiveCommand("AUTH XOAUTH2 " + new String(data),
345e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                    "AUTH XOAUTH2 /redacted/");
346e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        } catch (MessagingException me) {
347e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            if (me.getMessage().length() > 1 && me.getMessage().charAt(1) == '3') {
348e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon                throw new AuthenticationFailedException(me.getMessage());
349e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            }
350e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon            throw me;
351e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon        }
352e8eb6e659b5914eb7deab451c583e906010d0457Martin Hibdon    }
35396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
354