/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.mail.smtp; import com.caucho.server.util.CauchoSystem; import com.caucho.util.L10N; import com.caucho.vfs.ReadStream; import com.caucho.vfs.SocketStream; import com.caucho.vfs.WriteStream; import javax.mail.Address; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.URLName; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; /** * Resin's SMTP transport implementation. */ public class SmtpTransport extends Transport { private static final L10N L = new L10N(SmtpTransport.class); private static final Logger log = Logger.getLogger(SmtpTransport.class.getName()); private Socket _socket; private ReadStream _is; private WriteStream _os; public SmtpTransport(Session session, URLName urlName) { super(session, urlName); } /** * Connect for the protocol. */ protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException { if (host == null) host = "localhost"; if (port < 0) port = 25; // XXX: pooling if (_socket != null) throw new MessagingException(L.l("Attempted to connect to open connection.")); try { _socket = new Socket(host, port); _socket.setSoTimeout(10000); SocketStream s = new SocketStream(_socket); _os = new WriteStream(s); _is = new ReadStream(s, _os); String line = _is.readLine(); log.fine("smtp connection to " + host + ":" + port + " succeeded"); log.fine("smtp: " + line); _os.print("EHLO " + CauchoSystem.getLocalHost() + "\r\n"); _os.flush(); readResponse(); setConnected(true); } catch (IOException e) { log.fine("smtp connection to " + host + ":" + port + " failed: " + e); log.log(Level.FINER, e.toString(), e); throw new MessagingException("smtp connection to " + host + ":" + port + " failed.\n" + e); } return true; } /** * Sends a message to the specified recipients. * * @param msg the message to send * @param addresses the destination addresses */ public void sendMessage(Message msg, Address []addresses) throws MessagingException { if (! isConnected()) throw new MessagingException("Transport does not have an active connection."); if (! (msg instanceof MimeMessage)) throw new MessagingException("message must be a MimeMessage at '" + msg.getClass().getName() + "'"); MimeMessage mimeMsg = (MimeMessage) msg; try { // XXX: EHLO to resync? or RSET? // XXX: FROM String []fromList = mimeMsg.getHeader("From"); String from; if (fromList == null || fromList.length < 1) { // XXX: possible should have a default throw new MessagingException("message should have a sender"); } else from = fromList[0]; _os.print("MAIL FROM:<" + from + ">\r\n"); _os.flush(); if (log.isLoggable(Level.FINER)) log.finer("mail from:<" + from + ">"); readResponse(); for (int i = 0; i < addresses.length; i++) { InternetAddress addr = (InternetAddress) addresses[i]; if (log.isLoggable(Level.FINER)) log.finer("mail to:<" + addr.getAddress() + ">"); _os.print("RCPT TO:<" + addr.getAddress() + ">\r\n"); _os.flush(); readResponse(); } _os.print("DATA\r\n"); _os.flush(); String line = _is.readLine(); if (! line.startsWith("354 ")) throw new MessagingException("Data not accepted: " + line); mimeMsg.writeTo(new DataFilter(_os)); _os.print("\r\n.\r\n"); _os.flush(); } catch (IOException e) { log.log(Level.FINER, e.toString(), e); throw new MessagingException(e.toString()); } } private int readResponse() throws IOException, MessagingException { while (true) { String line = _is.readLine(); if (line.length() < 4) throw new MessagingException(line); int status = 0; for (int i = 0; i < 3; i++) { char ch; if ('0' <= (ch = line.charAt(i)) && ch <= '9') status = 10 * status + ch - '0'; } if ((status / 100) % 10 != 2) throw new MessagingException(line); if (line.charAt(3) != '-') return status; } } /** * Close connection. */ public void close() throws MessagingException { Socket socket = _socket; _socket = null; WriteStream os = _os; _os = null; setConnected(false); try { if (os != null) { os.print("QUIT\r\n"); os.flush(); } } catch (IOException e) { } try { if (socket != null) socket.close(); } catch (IOException e) { } } private class DataFilter extends OutputStream { private OutputStream _os; private boolean _isCr; private boolean _isLf; DataFilter(OutputStream os) { _os = os; } public void write(int ch) throws IOException { switch (ch) { case '\r': _isCr = true; _isLf = false; break; case '\n': _isLf = _isCr; _isCr = false; break; case '.': if (_isLf) _os.write('.'); _isLf = false; _isCr = false; break; default: _isLf = false; _isCr = false; break; } _os.write(ch); } } }