/* * 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.scripting.mail; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import javax.net.ssl.SSLSocket; import com.slamd.jobs.JSSEBlindTrustSocketFactory; import com.slamd.job.JobClass; import com.slamd.scripting.engine.Argument; import com.slamd.scripting.engine.Method; import com.slamd.scripting.engine.ScriptException; import com.slamd.scripting.engine.Variable; import com.slamd.scripting.general.BooleanVariable; import com.slamd.scripting.general.IntegerVariable; import com.slamd.scripting.general.StringArrayVariable; import com.slamd.scripting.general.StringVariable; /** * This class defines a variable that maintains a connection to a POP3 mail * server and allows for interaction with that server. A POP connection has the * the following methods: * * <UL> * <LI>authenticate(string userID, string password) -- Authenticates to the * POP3 server. This method returns a Boolean value indicating whether * the authentication was successful.</LI> * <LI>connect(string host, int port) -- Establishes a POP3 connection to the * mail server. Returns a Boolean value indicating whether the connection * was established successfully.</LI> * <LI>connect(string host, int port, boolean useSSL) -- Establishes a POP3 * connection to the mail server, optionally using SSL. Returns a Boolean * value indicating whether the connection was established * successfully.</LI> * <LI>delete(int messageID) -- Attempts to delete the specified message from * the POP3 server. This method returns a Boolean value indicating * whether the delete was successful.</LI> * <LI>disconnect() -- Closes the connection to the POP3 server. This method * does not return a value.</LI> * <LI>getFailureReason() -- Retrieves a string that provides information * about the reason for the last failure, if that is available.</LI> * <LI>list() -- Retrieves a list of the messages contained in the user's * inbox. This method returns a string array containing the lines of * output from the list command.</LI> * <LI>noOp() -- Sends a NOOP command to the server, which has no effect but * to prevent the connection from remaining idle for too long. This * method does not return a value.</LI> * <LI>retrieve(int messageID) -- Retrieves the specified message from the * user's inbox. The message will be returned as a mail message * object.</LI> * <LI>stat() -- Retrieves the number of messages contained in the user's * inbox. This method will return an integer indicating the number of * messages in the inbox.</LI> * <LI>top(int messageID, int lines) -- Retrieves the indicated number of * lines from the top of the specified message. This method will return * a string array containing the requested lines.</LI> * </UL> * * * @author Neil A. Wilson */ public class POPConnectionVariable extends Variable { /** * The name that will be used for the data type of POP connection variables. */ public static final String POP_CONNECTION_VARIABLE_TYPE = "popconnection"; /** * The name of the method that performs a POP3 authentication. */ public static final String AUTHENTICATE_METHOD_NAME = "authenticate"; /** * The method number for the "authenticate" method. */ public static final int AUTHENTICATE_METHOD_NUMBER = 0; /** * The name of the method that establishes a connection to the POP server. */ public static final String CONNECT_METHOD_NAME = "connect"; /** * The method number for the first "connect" method. */ public static final int CONNECT_1_METHOD_NUMBER = 1; /** * The method number for the second "connect" method. */ public static final int CONNECT_2_METHOD_NUMBER = 2; /** * The name of the method that can be used to delete a message from the POP3 * server. */ public static final String DELETE_METHOD_NAME = "delete"; /** * The method number for the "delete" method. */ public static final int DELETE_METHOD_NUMBER = 3; /** * The name of the method that can be used to disconnect from the POP3 server. */ public static final String DISCONNECT_METHOD_NAME = "disconnect"; /** * The method number for the "disconnect" method. */ public static final int DISCONNECT_METHOD_NUMBER = 4; /** * The name of the method that can be used to determine the reason for the * last failure. */ public static final String GET_FAILURE_REASON_METHOD_NAME = "getfailurereason"; /** * The method number for the "getFailureReason" method. */ public static final int GET_FAILURE_REASON_METHOD_NUMBER = 5; /** * The name of the method that can be used to list the messages in the POP3 * server. */ public static final String LIST_METHOD_NAME = "list"; /** * The method number for the "list" method. */ public static final int LIST_METHOD_NUMBER = 6; /** * The name of the method that sends a "NOOP" to the server. */ public static final String NOOP_METHOD_NAME = "noop"; /** * The method number for the "noop" method. */ public static final int NOOP_METHOD_NUMBER = 7; /** * The name of the method that can be used to retrieve a message in the POP3 * server. */ public static final String RETRIEVE_METHOD_NAME = "retrieve"; /** * The method number for the "retrieve" method. */ public static final int RETRIEVE_METHOD_NUMBER = 8; /** * The name of the method that can be used to determine the number of messages * in the user's inbox. */ public static final String STAT_METHOD_NAME = "stat"; /** * The method number for the "stat" method. */ public static final int STAT_METHOD_NUMBER = 9; /** * The name of the method that can be used to retrieve the top portion of a * mail message. */ public static final String TOP_METHOD_NAME = "top"; /** * The method number for the "top" method. */ public static final int TOP_METHOD_NUMBER = 10; /** * The set of methods associated with POP connection variables. */ public static final Method[] POP_CONNECTION_VARIABLE_METHODS = new Method[] { new Method(AUTHENTICATE_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE, StringVariable.STRING_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(CONNECT_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE, IntegerVariable.INTEGER_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(CONNECT_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE, IntegerVariable.INTEGER_VARIABLE_TYPE, BooleanVariable.BOOLEAN_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(DELETE_METHOD_NAME, new String[] { IntegerVariable.INTEGER_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(DISCONNECT_METHOD_NAME, new String[0], null), new Method(GET_FAILURE_REASON_METHOD_NAME, new String[0], StringVariable.STRING_VARIABLE_TYPE), new Method(LIST_METHOD_NAME, new String[0], StringArrayVariable.STRING_ARRAY_VARIABLE_TYPE), new Method(NOOP_METHOD_NAME, new String[0], null), new Method(RETRIEVE_METHOD_NAME, new String[] { IntegerVariable.INTEGER_VARIABLE_TYPE }, MailMessageVariable.MAIL_MESSAGE_VARIABLE_TYPE), new Method(STAT_METHOD_NAME, new String[0], IntegerVariable.INTEGER_VARIABLE_TYPE), new Method(TOP_METHOD_NAME, new String[] { IntegerVariable.INTEGER_VARIABLE_TYPE, IntegerVariable.INTEGER_VARIABLE_TYPE}, StringArrayVariable.STRING_ARRAY_VARIABLE_TYPE) }; /** * The end of line character as required by RFC 1725. */ public static final String EOL = "\r\n"; // The socket, reader, and writer used to communicate with the POP3 server. private BufferedReader reader; private BufferedWriter writer; private Socket socket; // The reason for the last failure experienced. private String failureReason; /** * Creates a new variable with no name, to be used only when creating a * variable with <CODE>Class.newInstance()</CODE>, and only when * <CODE>setName()</CODE> is called after that to set the name. * * @throws ScriptException If a problem occurs while initializing the new * variable. */ public POPConnectionVariable() throws ScriptException { // No implementation required. } /** * Retrieves the name of the variable type for this variable. * * @return The name of the variable type for this variable. */ @Override() public String getVariableTypeName() { return POP_CONNECTION_VARIABLE_TYPE; } /** * Retrieves a list of all methods defined for this variable. * * @return A list of all methods defined for this variable. */ @Override() public Method[] getMethods() { return POP_CONNECTION_VARIABLE_METHODS; } /** * Indicates whether this variable type has a method with the specified name. * * @param methodName The name of the method. * * @return <CODE>true</CODE> if this variable has a method with the specified * name, or <CODE>false</CODE> if it does not. */ @Override() public boolean hasMethod(String methodName) { for (int i=0; i < POP_CONNECTION_VARIABLE_METHODS.length; i++) { if (POP_CONNECTION_VARIABLE_METHODS[i].getName().equals(methodName)) { return true; } } return false; } /** * Retrieves the method number for the method that has the specified name and * argument types, or -1 if there is no such method. * * @param methodName The name of the method. * @param argumentTypes The list of argument types for the method. * * @return The method number for the method that has the specified name and * argument types. */ @Override() public int getMethodNumber(String methodName, String[] argumentTypes) { for (int i=0; i < POP_CONNECTION_VARIABLE_METHODS.length; i++) { if (POP_CONNECTION_VARIABLE_METHODS[i].hasSignature(methodName, argumentTypes)) { return i; } } return -1; } /** * Retrieves the return type for the method with the specified name and * argument types. * * @param methodName The name of the method. * @param argumentTypes The set of argument types for the method. * * @return The return type for the method, or <CODE>null</CODE> if there is * no such method defined. */ @Override() public String getReturnTypeForMethod(String methodName, String[] argumentTypes) { for (int i=0; i < POP_CONNECTION_VARIABLE_METHODS.length; i++) { if (POP_CONNECTION_VARIABLE_METHODS[i].hasSignature(methodName, argumentTypes)) { return POP_CONNECTION_VARIABLE_METHODS[i].getReturnType(); } } return null; } /** * Executes the specified method, using the provided variables as arguments * to the method, and makes the return value available to the caller. * * @param lineNumber The line number of the script in which the method * call occurs. * @param methodNumber The method number of the method to execute. * @param arguments The set of arguments to use for the method. * * @return The value returned from the method, or <CODE>null</CODE> if it * does not return a value. * * @throws ScriptException If the specified method does not exist, or if a * problem occurs while attempting to execute it. */ @Override() public Variable executeMethod(int lineNumber, int methodNumber, Argument[] arguments) throws ScriptException { switch (methodNumber) { case AUTHENTICATE_METHOD_NUMBER: StringVariable sv1 = (StringVariable) arguments[0].getArgumentValue(); StringVariable sv2 = (StringVariable) arguments[1].getArgumentValue(); String userID = sv1.getStringValue(); String userPW = sv2.getStringValue(); failureReason = null; try { writer.write("user " + userID + EOL); writer.flush(); String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading USER response"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to USER command"; } else { failureReason = "Error response to USER command: " + line; } return new BooleanVariable(false); } writer.write("pass " + userPW + EOL); writer.flush(); line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading PASS response"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to PASS command"; } else { failureReason = "Error response to PASS command: " + line; } return new BooleanVariable(false); } return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); return new BooleanVariable(false); } case CONNECT_1_METHOD_NUMBER: sv1 = (StringVariable) arguments[0].getArgumentValue(); IntegerVariable iv1 = (IntegerVariable) arguments[1].getArgumentValue(); String address = sv1.getStringValue(); int port = iv1.getIntValue(); failureReason = null; try { socket = new Socket(address, port); reader = new BufferedReader(new InputStreamReader( socket.getInputStream())); writer = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream())); String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading greeting line"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to greeting line"; } else { failureReason = "Error response on greeting line: " + line; } reader.close(); writer.close(); socket.close(); return new BooleanVariable(false); } return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); return new BooleanVariable(false); } case CONNECT_2_METHOD_NUMBER: sv1 = (StringVariable) arguments[0].getArgumentValue(); iv1 = (IntegerVariable) arguments[1].getArgumentValue(); BooleanVariable bv1 = (BooleanVariable) arguments[2].getArgumentValue(); address = sv1.getStringValue(); port = iv1.getIntValue(); boolean useSSL = bv1.getBooleanValue(); failureReason = null; try { if (useSSL) { JSSEBlindTrustSocketFactory socketFactory = new JSSEBlindTrustSocketFactory(); socket = socketFactory.makeSocket(address, port); } else { socket = new Socket(address, port); } reader = new BufferedReader(new InputStreamReader( socket.getInputStream())); writer = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream())); String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading greeting line"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to greeting line"; } else { failureReason = "Error response on greeting line: " + line; } reader.close(); writer.close(); socket.close(); return new BooleanVariable(false); } return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); return new BooleanVariable(false); } case DELETE_METHOD_NUMBER: iv1 = (IntegerVariable) arguments[0].getArgumentValue(); int messageID = iv1.getIntValue(); failureReason = null; try { writer.write("dele " + messageID + EOL); writer.flush(); String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading DELE response"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to DELE command"; } else { failureReason = "Error response to DELE command: " + line; } return new BooleanVariable(false); } return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); return new BooleanVariable(false); } case DISCONNECT_METHOD_NUMBER: failureReason = null; try { writer.write("quit" + EOL); writer.flush(); } catch (Exception e) {} try { reader.close(); writer.close(); socket.close(); } catch (Exception e) {} return null; case GET_FAILURE_REASON_METHOD_NUMBER: return new StringVariable(failureReason); case LIST_METHOD_NUMBER: StringArrayVariable sav = new StringArrayVariable(); failureReason = null; try { writer.write("list" + EOL); writer.flush(); while (true) { String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) == '-') || (line.charAt(0) == '.')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading LIST response"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to LIST command"; } else if (line.charAt(0) == '-') { failureReason = "Error response to LIST command: " + line; } break; } else if (line.charAt(0) != '+') { sav.addStringValue(line); } } } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); } return sav; case NOOP_METHOD_NUMBER: failureReason = null; try { writer.write("noop" + EOL); writer.flush(); reader.readLine(); } catch (Exception e) {} return null; case RETRIEVE_METHOD_NUMBER: iv1 = (IntegerVariable) arguments[0].getArgumentValue(); messageID = iv1.getIntValue(); MailMessageVariable mmv = new MailMessageVariable(); failureReason = null; try { writer.write("retr " + messageID + EOL); writer.flush(); String line = reader.readLine(); if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading RETR response"; return mmv; } else if (line.charAt(0) != '+') { failureReason = line; return mmv; } boolean blankSeen = false; while (true) { line = reader.readLine(); if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading RETR response"; break; } else if ((line.length() == 0) && (! blankSeen)) { blankSeen = true; } else if (line.equals(".")) { break; } else { if (blankSeen) { mmv.addBodyLine(line); } else { mmv.addHeaderLine(line); } } } } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); } return mmv; case STAT_METHOD_NUMBER: try { failureReason = null; writer.write("stat" + EOL); writer.flush(); String line = reader.readLine(); if ((line == null) || (line.length() == 0) || (line.charAt(0) != '+')) { if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading STAT response"; } else if (line.length() == 0) { failureReason = "Unexpected empty response to STAT command"; } else { failureReason = "Error response to STAT command: " + line; } return new IntegerVariable(-1); } int space1 = line.indexOf(' '); int space2 = line.indexOf(' ', space1+1); int returnValue = Integer.parseInt(line.substring(space1+1, space2)); return new IntegerVariable(returnValue); } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); return new IntegerVariable(-1); } case TOP_METHOD_NUMBER: iv1 = (IntegerVariable) arguments[0].getArgumentValue(); IntegerVariable iv2 = (IntegerVariable) arguments[1].getArgumentValue(); int numLines = iv2.getIntValue(); messageID = iv1.getIntValue(); sav = new StringArrayVariable(); try { writer.write("top " + messageID + ' ' + numLines + EOL); writer.flush(); String line = reader.readLine(); if (line == null) { failureReason = "Unexpected end of input stream from server " + "while reading RETR response"; return sav; } else if (line.charAt(0) != '+') { failureReason = line; return sav; } boolean blankSeen = false; while (true) { line = reader.readLine(); if (line == null) { break; } else if ((line.length() == 0) && (! blankSeen)) { blankSeen = true; } else if (line.charAt(0) == '.') { break; } else if ((line.charAt(0) != '+') && (blankSeen)) { sav.addStringValue(line); } } } catch (Exception e) { failureReason = "Caught exception: " + JobClass.stackTraceToString(e); } return sav; default: throw new ScriptException(lineNumber, "There is no method " + methodNumber + " defined for " + getArgumentType() + " variables."); } } /** * Assigns the value of the provided argument to this variable. The value of * the provided argument must be of the same type as this variable. * * @param argument The argument whose value should be assigned to this * variable. * * @throws ScriptException If a problem occurs while performing the * assignment. */ @Override() public void assign(Argument argument) throws ScriptException { if (! argument.getArgumentType().equals(POP_CONNECTION_VARIABLE_TYPE)) { throw new ScriptException("Attempt to assign an argument of type " + argument.getArgumentType() + " to a variable of type " + POP_CONNECTION_VARIABLE_TYPE + " rejected."); } POPConnectionVariable pcv = (POPConnectionVariable) argument.getArgumentValue(); socket = pcv.socket; reader = pcv.reader; writer = pcv.writer; } /** * Retrieves a string representation of the value of this argument. * * @return A string representation of the value of this argument. */ public String getValueAsString() { if (socket == null) { return "null"; } else { boolean connected = socket.isConnected(); if (! connected) { return "not connected"; } else { String host = socket.getInetAddress().getHostAddress(); int port = socket.getPort(); boolean usingSSL = (socket instanceof SSLSocket); if (usingSSL) { return "pops://" + host + ':' + port; } else { return "pop://" + host + ':' + port; } } } } }