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