/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.net.mail; import totalcross.io.*; import totalcross.net.*; import totalcross.sys.Convert; import totalcross.sys.InvalidNumberException; import totalcross.util.Properties; /** * This class implements the Transport abstract class using SMTP for message submission and transport. * * @since TotalCross 1.13 */ public class SMTPTransport extends Transport { Socket connection; DataStream writer; LineReader reader; int authSupported = 0; boolean supportsTLS; boolean requiresTLS; String lastServerResponse; private static final String ehlo = "EHLO ..." + Convert.CRLF; protected static final String starttls = "STARTTLS" + Convert.CRLF; protected SMTPTransport(MailSession session) { super(session); } protected void protocolConnect(String host, int port, String user, String password) throws AuthenticationException, MessagingException { if (supportsTLS) startTLS(); ehlo(); switch (authSupported) { case 1: // auth with LOGIN issueCommand("AUTH LOGIN" + Convert.CRLF, 334); issueCommand(Base64.encode(user.getBytes()) + Convert.CRLF, 334); issueCommand(Base64.encode(password.getBytes()) + Convert.CRLF, 235); break; case 2: // auth with PLAIN String auth = user + '\0' + user + '\0' + password; issueCommand("AUTH PLAIN " + Base64.encode(auth.getBytes()) + Convert.CRLF, 235); break; case 3: // auth with CRAM-MD5, not supported yet. default: throw new AuthenticationException("Unsupported authentication type."); } } public void sendMessage(Message message) throws MessagingException { try { // WRITE RETURN PATH Properties.Str smtpFrom = (Properties.Str) session.get(MailSession.SMTP_FROM); String returnPath = null; if (smtpFrom != null && smtpFrom.value != null) returnPath = smtpFrom.value; else { Address[] from = message.getFrom(); if (from != null && from.length > 0 && from[0].address != null) returnPath = from[0].address; else returnPath = ConnectionManager.getLocalHost(); } issueCommand("MAIL FROM:<" + returnPath + ">" + Convert.CRLF, 250); // RCPT TO for (int i = message.recipients.size() - 1; i >= 0; i--) issueCommand("RCPT TO:<" + ((String) message.recipients.items[i]) + ">" + Convert.CRLF, 250); // START DATA issueCommand("DATA" + Convert.CRLF, 354); // WRITE MESSAGE message.writeTo(writer); // END DATA connection.readTimeout = 40000; //flsobral: some SMTP servers are really slow to reply the message terminator, so we wait a little longer here. This is NOT related to the device connection. issueCommand(Convert.CRLF + "." + Convert.CRLF, 250); } catch (IOException e) { throw new MessagingException(e); } } protected boolean ehlo() throws MessagingException { boolean ret = simpleCommand(ehlo) == 220; while (readServerResponse() == 220) ; // the HELO reply may be followed by textual messages, just ignore them. authSupported = 0; do { int start = lastServerResponse.indexOf("AUTH"); if (start != -1) { if ((start = lastServerResponse.indexOf("LOGIN")) != -1) authSupported = 1; else if ((start = lastServerResponse.indexOf("PLAIN")) != -1) authSupported = 2; else if ((start = lastServerResponse.indexOf("CRAM-MD5")) != -1) authSupported = 3; else authSupported = -1; } else if (lastServerResponse.indexOf("STARTTLS") != -1) supportsTLS = true; //flsobral: server supports secure connections readServerResponse(); } while (lastServerResponse.charAt(3) != ' '); requiresTLS = supportsTLS && authSupported == 0; return ret; } public void connect(Socket connection) throws MessagingException { boolean tlsEnabled = ((Properties.Boolean) session.get(MailSession.SMTP_STARTTLS)).value; this.connection = connection; this.writer = new DataStream(connection, true); try { reader = new LineReader(connection); if (!ehlo()) throw new MessagingException("Failed to greet the remote server."); if (requiresTLS && !tlsEnabled) throw new MessagingException( "Server requires authentication through a secure connection - See MailSession.SMTP_STARTTLS"); } catch (IOException e) { throw new MessagingException(e); } } protected int readServerResponse() throws MessagingException { try { lastServerResponse = reader.readLine(); return Convert.toInt(lastServerResponse.substring(0, 3)); } catch (InvalidNumberException e) { throw new MessagingException(e.getMessage() + "\n Reply: " + lastServerResponse); } catch (IOException e) { throw new MessagingException(e); } } public void issueCommand(String cmd, int expect) throws MessagingException { int responseCode = simpleCommand(cmd.getBytes()); if (expect != -1 && responseCode != expect) throw new MessagingException("Unexpected response code. Expected " + expect + ", but received " + responseCode); } protected int simpleCommand(byte[] command) throws MessagingException { try { writer.writeBytes(command); return readServerResponse(); } catch (IOException e) { throw new MessagingException(e); } } public int simpleCommand(String command) throws MessagingException { return simpleCommand(command.getBytes()); } public boolean supportsExtension(String ext) { if ("STARTTLS".equals(ext)) return supportsTLS; return false; } public boolean getRequireStartTLS() { return requiresTLS; } public void connect() throws AuthenticationException, MessagingException { // TODO Auto-generated method stub } /* * This method MUST ensure the service is completely invalidated and closed, even when an exception is thrown. That's * why each command is issued inside a try/catch block. (non-Javadoc) * * @see totalcross.net.mail.Service#close() */ public void close() throws MessagingException { MessagingException exception = null; try { // QUIT issueCommand("QUIT" + Convert.CRLF, 221); } catch (MessagingException e) { exception = e; } try { writer.close(); } catch (IOException e) { if (exception != null) exception = new MessagingException(e); } try { connection.close(); } catch (IOException e) { if (exception != null) exception = new MessagingException(e); } reader = null; writer = null; connection = null; if (exception != null) throw exception; } public void connect(String host, int port, String user, String password) throws AuthenticationException, MessagingException { // TODO Auto-generated method stub } /** * Issue the STARTTLS command and switch the socket to TLS mode if it succeeds. * * @throws MessagingException */ protected void startTLS() throws MessagingException {} }