/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.server; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; import com.slamd.common.Constants; import com.slamd.db.SLAMDDB; import com.slamd.job.JobClass; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.IntegerParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.parameter.StringParameter; /** * This class provides a means of sending an e-mail message over SMTP as defined * in RFC 821. * * * @author Neil A. Wilson */ public class SMTPMailer implements ConfigSubscriber { /** * The name used to register the mailer as a subscriber to the configuration * handler. */ public static final String CONFIG_SUBSCRIBER_NAME = "SLAMD Mailer"; // Indicates whether the mailer will be enabled for use in the SLAMD server. private boolean enableMailer; // The port number to use to contact the SMTP server when sending mail. private int mailPort; // The date formatter that will be used to format dates in the RFC-822 format. private SimpleDateFormat dateFormat; // The configuration database with which this mailer is associated. private SLAMDDB configDB; // The SLAMD server with which this mailer is associated. private SLAMDServer slamdServer; // The base URI for the SLAMD server's admin interface. private String servletBaseURI; // The address from which mail messages will be sent. private String fromAddress; // The address of this system, which will be sent in HELO messages. private String localAddress; // The address of the mail server to which admin alert messages will be sent. private String mailHost; /** * Creates a new instance of this SMTP mailer. * * @param slamdServer The SLAMD server instance with which this mailer is * associated. */ public SMTPMailer(SLAMDServer slamdServer) { this.slamdServer = slamdServer; configDB = slamdServer.getConfigDB(); dateFormat = new SimpleDateFormat(Constants.MAIL_DATE_FORMAT); // Set default values for all the configurable parameters. enableMailer = false; mailPort = Constants.DEFAULT_SMTP_PORT; mailHost = ""; fromAddress = ""; servletBaseURI = ""; configDB.registerAsSubscriber(this); refreshSubscriberConfiguration(); // Get the local address that will be used in HELO messages. try { localAddress = InetAddress.getLocalHost().getHostName(); } catch (IOException ioe) { try { localAddress = InetAddress.getLocalHost().getHostAddress(); } catch (IOException ioe2) { localAddress = "slamd_server"; } } } /** * Indicates whether this mailer is currently enabled. * * @return <CODE>true</CODE> if this mailer is enabled, or <CODE>false</CODE> * if it is not. */ public boolean isEnabled() { return enableMailer; } /** * Retrieves the URL that can be used to access the admin interface. * * @return The URL that can be used to access the admin interface. */ public String getServletBaseURI() { return servletBaseURI; } /** * Sends a mail message to all configured recipients to notify them that the * specified critical event has occurred. * * @param recipients The addresses of the recipients to which the message * should be sent. * @param subject The subject to use for the message. * @param message The body of the mail message to be sent. */ public void sendMessage(String[] recipients, String subject, String message) { if (! enableMailer) { return; } try { // Open the connection to the SMTP server. Socket socket = new Socket(mailHost, mailPort); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); // The SMTP server should first introduce itself to the client. Make sure // that the introduction is acceptable -- if so then it should start with // the number "220". String serverResponse = readLine(reader); if (! serverResponse.startsWith("220")) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "provided an invalid hello message."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // Send a "HELO" request to the server and read the response. Make sure // that the response starts with a "250". sendLine(writer, "HELO " + localAddress); serverResponse = readLine(reader); if (! serverResponse.startsWith("250")) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "provided an invalid hello response."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // Specify the from address. The server must provide a response starting // with "250" for this to be acceptable. sendLine(writer, "MAIL FROM:<" + fromAddress + '>'); serverResponse = readLine(reader); if (! serverResponse.startsWith("250")) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "provided an invalid MAIL FROM response " + "message."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // Specify the recipients. The server should provide a response starting // with "250" or "251" for each of them. boolean onePassed = false; for (int i=0; i < recipients.length; i++) { sendLine(writer, "RCPT TO:<" + recipients[i] + '>'); serverResponse = readLine(reader); if (! serverResponse.startsWith("25")) { slamdServer.logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Unable to send mail message to " + recipients[i] + " -- mail server did not " + "accept that recipient address."); } else { onePassed = true; } } if (! onePassed) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "rejected all recipient addresses."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // Send the "DATA" header to the server. The server must provide a // response starting with "354". sendLine(writer, "DATA"); serverResponse = readLine(reader); if (! serverResponse.startsWith("354")) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "returned an invalid DATA intermediate " + "response message."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // Send the message header. The server will not provide a response to // this. sendLine(writer, "From: <" + fromAddress + '>'); sendLine(writer, "MIME-Version: 1.0"); sendLine(writer, "Content-type: text/plain; charset=us-ascii"); sendLine(writer, "Date: " + dateFormat.format(new Date())); sendLine(writer, "Subject: " + subject); for (int i=0; i < recipients.length; i++) { sendLine(writer, "To: <" + recipients[i] + '>'); } sendLine(writer, ""); // Send the message body, followed by a line containing only a period. // The server should provide a response starting with "250". sendLine(writer, message); sendLine(writer, ""); sendLine(writer, "."); serverResponse = readLine(reader); if (! serverResponse.startsWith("250")) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- mail server " + "provided an invalid DATA complete response " + "message."); sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); return; } // The message is complete, so end the session with a "QUIT". sendLine(writer, "QUIT"); writer.close(); reader.close(); socket.close(); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- I/O error wile " + "interacting with the server: " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); } catch (Exception e) { slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to send mail message -- uncaught " + "exception while interacting with the server: " + e); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); } } /** * Writes the provided message to the SMTP server, appending the correct * end-of-line character, and flushing the buffer to ensure that it is sent. * * @param writer The buffered writer to use to send the text. * @param line The line of text to be sent to the server. * * @throws IOException If an I/O error occurs while attempting to send the * line to the server. */ private void sendLine(BufferedWriter writer, String line) throws IOException { writer.write(line); writer.write(Constants.SMTP_EOL); writer.flush(); } /** * Reads a one-line response from the SMTP server. * * @param reader The buffered reader used to read information from the * server. * * @return The response from the server. * * @throws IOException If an I/O error occurs while attempting to read data * from the server. */ private String readLine(BufferedReader reader) throws IOException { return reader.readLine(); } /** * Retrieves the name that the scheduler uses to subscribe to the * configuration handler in order to be notified of configuration changes. * * @return The name that the scheduler uses to subscribe to the configuration * handler in order to be notified of configuration changes. */ public String getSubscriberName() { return CONFIG_SUBSCRIBER_NAME; } /** * Retrieves the set of configuration parameters associated with this * configuration subscriber. * * @return The set of configuration parameters associated with this * configuration subscriber. */ public ParameterList getSubscriberParameters() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In SMTPMailer.getParameters()"); BooleanParameter enableMailerParameter = new BooleanParameter(Constants.PARAM_ENABLE_MAIL_ALERTS, "Enable SLAMD Mailer", "Indicates whether the SLAMD mailer may be " + "used to send mail messages whenever certain " + "events occur.", enableMailer); IntegerParameter portParameter = new IntegerParameter(Constants.PARAM_SMTP_PORT, "SMTP Server Port", "The port number that should be used to " + "contact the SMTP server.", true, mailPort, true, 1, true, 65535); StringParameter hostParameter = new StringParameter(Constants.PARAM_SMTP_SERVER, "SMTP Server Address", "The address that should be used to contact the " + "SMTP server.", true, mailHost); StringParameter fromAddressParameter = new StringParameter(Constants.PARAM_MAIL_FROM_ADDRESS, "From Address", "The e-mail address that should be used as the " + "sender address for messages sent from the " + "SLAMD server.", true, fromAddress); StringParameter servletBaseURIParameter = new StringParameter(Constants.PARAM_SERVLET_BASE_URI, "Servlet Base URI", "The URI that may be used to access the admin " + "interface for the SLAMD server. It may be a " + "URL to a read-only version of the server if " + "desired.", false, servletBaseURI); Parameter[] params = new Parameter[] { enableMailerParameter, hostParameter, portParameter, fromAddressParameter, servletBaseURIParameter }; return new ParameterList(params); } /** * Re-reads all configuration information used by the SLAMD scheduler. In * this case, the only option is the delay between iterations of the scheduler * loop. */ public void refreshSubscriberConfiguration() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In SMTPMailer.refreshConfiguration()"); String paramValue = configDB.getConfigParameter(Constants.PARAM_ENABLE_MAIL_ALERTS); if ((paramValue != null) && (paramValue.length() > 0)) { enableMailer = (! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE)); } else { enableMailer = false; } paramValue = configDB.getConfigParameter(Constants.PARAM_SMTP_SERVER); if ((paramValue != null) && (paramValue.length() > 0)) { mailHost = paramValue; } else { enableMailer = false; } paramValue = configDB.getConfigParameter(Constants.PARAM_SMTP_PORT); if ((paramValue != null) && (paramValue.length() > 0)) { try { mailPort = Integer.parseInt(paramValue); } catch (NumberFormatException nfe) { enableMailer = false; slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { mailPort = Constants.DEFAULT_SMTP_PORT; } paramValue = configDB.getConfigParameter(Constants.PARAM_MAIL_FROM_ADDRESS); if ((paramValue != null) && (paramValue.length() > 0)) { fromAddress = paramValue; } else { enableMailer = false; } paramValue = configDB.getConfigParameter(Constants.PARAM_SERVLET_BASE_URI); if ((paramValue != null) && (paramValue.length() > 0)) { servletBaseURI = paramValue; } else { servletBaseURI = ""; } } /** * Re-reads the configuration information for the specified parameter. In * this case, the only option is the delay between iterations of the scheduler * loop. * * @param parameterName The name of the parameter to re-read from the * configuration. */ public void refreshSubscriberConfiguration(String parameterName) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In SMTPMailer.refreshConfiguration(" + parameterName + ')'); if (parameterName.equalsIgnoreCase(Constants.PARAM_ENABLE_MAIL_ALERTS)) { if ((mailHost == null) || (mailHost.length() == 0) || (fromAddress == null) || (fromAddress.length() == 0)) { enableMailer = false; return; } String paramValue = configDB.getConfigParameter(Constants.PARAM_ENABLE_MAIL_ALERTS); if ((paramValue != null) && (paramValue.length() > 0)) { enableMailer = (! paramValue.equalsIgnoreCase(Constants.CONFIG_VALUE_FALSE)); } else { enableMailer = false; } } else if (parameterName.equalsIgnoreCase(Constants.PARAM_SMTP_SERVER)) { String paramValue = configDB.getConfigParameter(Constants.PARAM_SMTP_SERVER); if ((paramValue != null) && (paramValue.length() > 0)) { mailHost = paramValue; } else { enableMailer = false; } } else if (parameterName.equalsIgnoreCase(Constants.PARAM_SMTP_PORT)) { String paramValue = configDB.getConfigParameter(Constants.PARAM_SMTP_PORT); if ((paramValue != null) && (paramValue.length() > 0)) { try { mailPort = Integer.parseInt(paramValue); } catch (NumberFormatException nfe) { enableMailer = false; slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { mailPort = Constants.DEFAULT_SMTP_PORT; } } else if (parameterName.equalsIgnoreCase(Constants.PARAM_MAIL_FROM_ADDRESS)) { String paramValue = configDB.getConfigParameter(Constants.PARAM_MAIL_FROM_ADDRESS); if ((paramValue != null) && (paramValue.length() > 0)) { fromAddress = paramValue; } else { enableMailer = false; } } else if (parameterName.equalsIgnoreCase(Constants.PARAM_SERVLET_BASE_URI)) { String paramValue = configDB.getConfigParameter(Constants.PARAM_SERVLET_BASE_URI); if ((paramValue != null) && (paramValue.length() > 0)) { servletBaseURI = paramValue; } else { servletBaseURI = ""; } } } }