001 package com.sptci.system;
002
003 import java.io.FileInputStream;
004 import java.io.InputStream;
005 import java.io.InputStreamReader;
006 import java.io.IOException;
007 import java.io.OutputStream;
008 import java.io.PrintStream;
009 import java.net.SocketException;
010
011 import java.util.Properties;
012 import java.util.logging.Level;
013 import java.util.logging.Logger;
014
015 import org.apache.commons.net.telnet.TelnetClient;
016
017 /**
018 * The class that is used to interact with the system. Provides methods
019 * to log user to the system and execute the password change command.
020 *
021 * <p>Modified version of class documented in
022 * <a href='http://www.informit.com/guides/content.asp?g=java&seqNum=40&rl=1'>informit.com</a>.</p>
023 * @author Rakesh Vidyadharan 2007-04-21
024 * @version $Id: CommandExecutor.java 3252 2007-05-12 19:12:31Z rakesh $
025 */
026 public class CommandExecutor
027 {
028 /**
029 * The logger to use to log errors/messages to.
030 */
031 private static final Logger logger =
032 Logger.getLogger( CommandExecutor.class.getName() );
033
034 /**
035 * The name of the system property that points to the properties file
036 * used to configure this instance.
037 *
038 * {@value}
039 */
040 public static final String PROPERTY_FILE = "password.properties.file";
041
042 /**
043 * The name of the property that contains the hostname of the server to
044 * which a <code>telnet</code> session will be initiated.
045 *
046 * {@value}
047 */
048 public static final String SERVER = "server";
049
050 /**
051 * The name of the property that contains the port on which a
052 * <code>telnet</code> session will be established. This propery is
053 * optional. By default a connection to the standard <code>telnet</code>
054 * port <code>23</code> will be attempted.
055 *
056 * {@value}
057 */
058 public static final String PORT = "port";
059
060 /**
061 * The name of the property that contains the login prompt sent back by
062 * telnet server when a connection attempt is made.
063 *
064 * {@value}
065 */
066 public static final String LOGIN_PROMPT = "login";
067
068 /**
069 * The name of the property that contains the password prompt sent back
070 * by telnet server after a response is sent to the {@link #LOGIN_PROMPT}
071 *
072 * {@value}
073 */
074 public static final String PASSWORD_PROMPT = "password";
075
076 /**
077 * The name of the property that contains the shell prompt character used
078 * by the system.
079 *
080 * <p><b>Note:</b> This of course depends upon the shell used by the
081 * user and the user's settings.</p>
082 *
083 * {@value}
084 */
085 public static final String PROMPT = "prompt";
086
087 /**
088 * The server to log on to affect the change in password.
089 */
090 final String server;
091
092 /**
093 * The port on which to connect to the {@link #server}.
094 */
095 final int port;
096
097 /**
098 * The user who is trying to change his password.
099 */
100 String user;
101
102 /**
103 * The telnet client instance to use to communicate with the {@link
104 * #server}.
105 */
106 private TelnetClient client = new TelnetClient();
107
108 /**
109 * The properties object that contains the configured properties for
110 * this instance.
111 */
112 final Properties properties;
113
114 /**
115 * The <code>InputStream</code> for the <code>telnet</code> session.
116 */
117 private InputStream in;
118
119 /**
120 * The <code>OutputStream</code> for the <code>telnet</code> session.
121 */
122 private PrintStream out;
123
124 /**
125 * Create a new instance of the class;
126 *
127 * @throws RuntimeException If errors are encountered while initialising
128 * the instance from the {@link #PROPERTY_FILE} file.
129 */
130 public CommandExecutor() throws RuntimeException
131 {
132 properties = new Properties();
133
134 try
135 {
136 properties.loadFromXML(
137 new FileInputStream( System.getProperty( PROPERTY_FILE ) ) );
138 }
139 catch ( Throwable t )
140 {
141 logger.log( Level.SEVERE, "Error loading properties file", t );
142 throw new RuntimeException( t );
143 }
144
145 server = properties.getProperty( SERVER );
146 port = Integer.parseInt( properties.getProperty( PORT, "23" ) );
147 }
148
149 /**
150 * Connect to the {@link #server} via <code>telnet</code> and log the
151 * user to the system. This method assumes that caller has either
152 * invoked {@link #setUser} or {@link #connect( String, String )} prior
153 * to calling this method.
154 *
155 * @see #connect( String, String )
156 * @throws SocketException If the socket timeout could not be set.
157 * @throws IOException If the socket could not be opened. In most cases
158 * you will only want to catch IOException since SocketException is
159 * derived from it.
160 * @throws AuthenticationFailedException If the login attempt failed due
161 * to username or password error.
162 */
163 public void connect( String password )
164 throws SocketException, IOException, AuthenticationFailedException
165 {
166 connect( user, password );
167 }
168
169 /**
170 * Connect to the {@link #server} via <code>telnet</code> and log the
171 * user to the system.
172 *
173 * @see #readUntil
174 * @see #write
175 * @see #readLoginResponse
176 * @throws SocketException If the socket timeout could not be set.
177 * @throws IOException If the socket could not be opened. In most cases
178 * you will only want to catch IOException since SocketException is
179 * derived from it.
180 * @throws AuthenticationFailedException If the login attempt failed due
181 * to username or password error.
182 */
183 public void connect( String user, String password )
184 throws SocketException, IOException, AuthenticationFailedException
185 {
186 setUser( user );
187 client.connect( server, port );
188
189 in = client.getInputStream();
190 out = new PrintStream( client.getOutputStream() );
191
192 readUntil( properties.getProperty( LOGIN_PROMPT ) );
193 write( user );
194 readUntil( properties.getProperty( PASSWORD_PROMPT ) );
195 write( password );
196 readLoginResponse();
197 }
198
199 /**
200 * Change the {@link #user}'s password.
201 *
202 * @param password The current password of the user.
203 * @param newpassword The new password to set.
204 * @throws IOException If the socket could not be opened. In most cases
205 * you will only want to catch IOException since SocketException is
206 * derived from it.
207 * @throws PasswordException If the current or new password are incorrect
208 * or insufficient
209 */
210 public void password( String password, String newpassword )
211 throws IOException, PasswordException
212 {
213 write( "passwd" );
214 readUntil( "password:" );
215 write( password );
216 readPasswordResponse( "password:", "Sorry", "Incorrect current password" );
217 write( newpassword );
218 readPasswordResponse( "password:", "Please try again", "Insufficient new password" );
219 write( newpassword );
220 readPasswordResponse( properties.getProperty( PROMPT ) + " ",
221 "Sorry", "Incorrect current password" );
222 }
223
224 /**
225 * Disconnect from the <code>telnet</code> session.
226 */
227 public void disconnect()
228 {
229 try
230 {
231 if ( client.isConnected() ) client.disconnect();
232 }
233 catch ( Throwable t )
234 {
235 logger.log( Level.WARNING,
236 "Error closing telnet connection for user: " + user, t );
237 }
238 }
239
240 /**
241 * Read the response from the server until the specified pattern is
242 * found.
243 *
244 * @param pattern The pattern until which the response from the server
245 * is to be read
246 * @throws IOException If errors are encountered while reading/writing
247 * to the socket streams.
248 */
249 private String readUntil( String pattern ) throws IOException
250 {
251 String result = null;
252 char lastChar = pattern.charAt( pattern.length() - 1 );
253 StringBuilder builder = new StringBuilder();
254 boolean found = false;
255 char ch = (char) in.read();
256
257 while ( true )
258 {
259 //System.out.print( ch );
260 builder.append( ch );
261
262 if ( ch == lastChar )
263 {
264 if ( builder.toString().toLowerCase().endsWith( pattern.toLowerCase() ) )
265 {
266 result = builder.toString();
267 break;
268 }
269 }
270
271 ch = (char) in.read();
272 }
273
274 return result;
275 }
276
277 /**
278 * Read the response from the server after a login attempt. Handle
279 * either a successful login or a failed login. Failed logins are
280 * indicated when the system responds with a fresh login prompt along with
281 * an error message.
282 *
283 * @throws IOException If errors are encountered while reading/writing
284 * to the socket streams.
285 * @throws AuthenticationFailedException If the login attempt failed due
286 * to username or password error.
287 */
288 private String readLoginResponse()
289 throws IOException, AuthenticationFailedException
290 {
291 String result = null;
292 String pattern = properties.getProperty( PROMPT ) + " ";
293 char lastChar = pattern.charAt( pattern.length() - 1 );
294 String error = "Login incorrect";
295 char lastCharError = error.charAt( error.length() - 1 );
296 StringBuilder builder = new StringBuilder();
297 boolean found = false;
298 char ch = (char) in.read();
299
300 while ( true )
301 {
302 //System.out.print( ch );
303 builder.append( ch );
304
305 if ( ch == lastChar )
306 {
307 if ( builder.toString().toLowerCase().endsWith( pattern.toLowerCase() ) )
308 {
309 result = builder.toString();
310 break;
311 }
312 }
313 else if ( ch == lastCharError )
314 {
315 if ( builder.toString().endsWith( error ) )
316 {
317 result = builder.toString();
318 throw new AuthenticationFailedException( "Login failed." );
319 }
320 }
321
322 ch = (char) in.read();
323 }
324
325 return result;
326 }
327
328 /**
329 * Read the response from the server after the current password has
330 * been entered during a password change request.
331 *
332 * @param success The pattern that indicates a successful entry
333 * @param error The pattern that indicates an error
334 * @param message The message to use in {@link PasswordException}.
335 * @throws IOException If errors are encountered while reading/writing
336 * to the socket streams.
337 * @throws PasswordException If the current password specified is
338 * incorrect.
339 */
340 private String readPasswordResponse( String success, String error,
341 String message ) throws IOException, PasswordException
342 {
343 String result = null;
344 char lastChar = success.charAt( success.length() - 1 );
345 char lastCharError = error.charAt( error.length() - 1 );
346
347 StringBuilder builder = new StringBuilder();
348 boolean found = false;
349 char ch = (char) in.read();
350
351 while ( true )
352 {
353 //System.out.print( ch );
354 builder.append( ch );
355
356 if ( ch == lastChar )
357 {
358 if ( builder.toString().toLowerCase().endsWith( success.toLowerCase() ) )
359 {
360 result = builder.toString();
361 break;
362 }
363 }
364 else if ( ch == lastCharError )
365 {
366 if ( builder.toString().toLowerCase().endsWith( error.toLowerCase() ) )
367 {
368 result = builder.toString();
369 throw new PasswordException( message );
370 }
371 }
372
373 ch = (char) in.read();
374 }
375
376 return result;
377 }
378
379 /**
380 * Write a response to the <code>telnet OutputStream</code>.
381 *
382 * @param response The response that is to be written to the stream.
383 * @throws IOException If errors are encountered while reading/writing
384 * to the socket streams.
385 */
386 private void write( String response ) throws IOException
387 {
388 out.println( response );
389 out.flush();
390 //System.out.println( response );
391 }
392
393 /**
394 * Send a command for execution to the telnet session.
395 *
396 * @see #write
397 * @see #readUntil
398 * @param command The command that is to be executed.
399 * @throws IOException If errors are encountered while sending the
400 * command for execution.
401 */
402 private String execute( String command ) throws IOException
403 {
404 write( command );
405 return readUntil( PROMPT + " " );
406 }
407
408 /**
409 * Returns {@link #user}.
410 *
411 * @return The value/reference of/to user.
412 */
413 public String getUser()
414 {
415 return user;
416 }
417
418 /**
419 * Set {@link #user}.
420 *
421 * @param user The value to set.
422 */
423 public void setUser( String user )
424 {
425 this.user = user;
426 }
427 }