/* PasswordClient.java This class provides both the core of Ganymede password changing clients and a command line client. Created: 28 January 1998 Module By: Michael Mulvaney ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program 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. This program 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. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package arlut.csd.ganymede.client.password; import java.io.Console; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.rmi.RemoteException; import java.util.Properties; import arlut.csd.Util.TranslationService; import arlut.csd.ganymede.client.ClientBase; import arlut.csd.ganymede.client.ClientEvent; import arlut.csd.ganymede.client.ClientListener; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryDataNode; import arlut.csd.ganymede.common.QueryResult; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.rmi.db_object; import arlut.csd.ganymede.rmi.pass_field; /*------------------------------------------------------------------------------ class PasswordClient ------------------------------------------------------------------------------*/ /** * <p>The core of a user password changing applet / application, whether * command line or GUI.</p> * * <p>This class can be invoked from the command line to act as an * interactive CLI password change client.</p> */ public class PasswordClient implements ClientListener { final static boolean debug = false; /** * TranslationService object for handling string localization in * the Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.password.PasswordClient"); /** * URL of the Ganymede server's RMI port to connect to. */ private static String url; // --- /** * <p>A ClientBase object forms the core of any Ganymede * client. It does the work to get us connected and * logged into the server, and serves as a reference * point for the server to talk to if something * unusual happens.</p> */ ClientBase client = null; /* -- */ public PasswordClient(String serverURL) throws RemoteException { client = new ClientBase(serverURL, this); } /** * <p>This method actually does the work of logging into the server, * changing the password, committing the transaction, and * disconnecting.</p> * * <p>There are three basic steps involved in changing the password. * First, the client must log on to the system, getting a handle on * the Session object. Next, we get a handle on the password field * of the user object, and change the value. Finally, we commit the * transaction.</p> */ public boolean changePassword(String username, String oldPassword, String newPassword) { arlut.csd.ganymede.rmi.Session session; /* -- */ if (debug) { System.out.println(" logging into server"); } try { client.connect(); } catch (Exception ex) { // "Connection to Ganymede server failed: {0}" error(ts.l("changePassword.connection_fail", ex.getMessage())); return false; } try { // First we need a referrence to a Session object. This is // accomplished through the ClientBase's login method. session = client.login(username, oldPassword); // If the username/password combination doesn't match, then // the session object will be null. For the purposes of this // client, it is suficient to just return false and require // the user to rerun the password client if the session is null. if (session == null) { if (debug) { System.out.println(" logged in, looking for :" + username + ":"); } // "Wrong password." error(ts.l("changePassword.wrong_pass")); return false; } // Now that we have a session object, we need to open a // transaction. All changes must be made with an open // tranaction. session.openTransaction("PasswordClient"); // In order to change the password, we must first get a handle // on the user object. This is accomplished through the // server's Query engine. QueryResult results = session.query(new Query(SchemaConstants.UserBase, new QueryDataNode(SchemaConstants.UserUserName, QueryDataNode.EQUALS, username))); if (results == null) { // "No user {0} found, can''t change password." System.out.println(ts.l("changePassword.no_such_user", username)); return false; } else if (results.size() != 1) { System.out.println("Error, found multiple matching user records.. can't happen?"); return false; } if (debug) { System.out.println("Changing password"); } // Invid's are id numbers for objects, the basic way to // referrencing objects in the server. Invid invid = results.getInvid(0); // To edit the object, we must check out the user through // the Session object. ReturnVal retVal = session.edit_db_object(invid); db_object user = (db_object) retVal.getObject(); // If edit_db_object returns a null object, it usually // means someone else is editing the object. It could // also mean that the user doesn't have sufficient // permission to edit the object. if (user == null) { // "Could not get handle on user object. Someone else might be editing it." error(ts.l("changePassword.locked")); session.abortTransaction(); return false; } // pass_field is a subclass of db_field, which represents // the fields in each object. We need a referrence to the // user's password field, so we can change it. pass_field pass = user.getPassField(SchemaConstants.UserPassword); // Changes to objects on the server return a ReturnVal. // ReturnVal contains information about the change just // made, including a list of fields that may have changed // as a result of this change, or dialogs prompting the // user for more information. // For the purposes of this application, we don't care // about the extra stuff in ReturnVal; we only want to // know if the password change worked or not. ReturnVal returnValue = pass.setPlainTextPass(newPassword); if (!ReturnVal.didSucceed(returnValue)) { if (debug) { error("It didn't work."); } String resultText = returnValue.getDialogText(); if (resultText != null && !resultText.equals("")) { System.err.println(resultText); } return false; } // After making changes to the database, the session changes // must be committed. This also returns a ReturnVal. ReturnVal rv = session.commitTransaction(); if (ReturnVal.didSucceed(rv)) { if (debug) { System.out.println("It worked."); } return true; } else { error("Error commiting transaction, password change failed."); } } catch (RemoteException ex) { error("Caught remote exception in authenticate: " + ex); } finally { try { client.disconnect(); } catch (RemoteException ex) { } } return false; } /** * <p>Send output to this.</p> * * <p>This just prints out the message, but could be directed to a * dialog or something later.</p> */ public void error(String message) { System.out.println(message); } // *** // // The following two methods comprise the ClientListener interface // which we need to implement to give the ClientBase object someone // to talk to. // // *** /** * <p>Called when the server forces a disconnect.</p> * * <p>Call getMessage() on the ClientEvent to get the reason for the * disconnect.</p> * * @see arlut.csd.ganymede.client.ClientListener * @see arlut.csd.ganymede.client.ClientEvent */ public void disconnected(ClientEvent e) { // "Error, the server forced us to disconnect. {0}" error(ts.l("disconnected.kicked", e.getMessage())); } /** * <p>Called when the ClientBase needs to report something to the * client. The client is expected to then put up a dialog or do * whatever else is appropriate.</p> * * <p>Call getMessage() on the ClientEvent to get the message.</p> * * @see arlut.csd.ganymede.client.ClientListener * @see arlut.csd.ganymede.client.ClientEvent */ public void messageReceived(ClientEvent e) { // "Server: {0}" // error(ts.l("messageReceived", e.getMessage())); } /** * <p>If this class is run from the command line, it will act as a * text-mode password client.</p> */ public static void main(String argv[]) { PasswordClient client = null; /* -- */ if (argv.length != 2) { // "Wrong number of command line parameters: required parameters are <properties> <user>" System.err.println(ts.l("main.args_error")); System.exit(0); } // Get the server URL loadProperties(argv[0]); /* RMI initialization stuff. */ /* This causes problems in Java 1.2. System.setSecurityManager(new RMISecurityManager());*/ // Create the client try { client = new PasswordClient(url); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException("Couldn't connect to authentication server.. " + ex.getMessage()); } try { // get old password, new password Console cons = System.console(); PrintWriter out = cons.writer(); String oldPassword = null; String newPassword = null; String verifyPassword = null; // Get the old password // "Old password:" oldPassword = new String(cons.readPassword("%s ", ts.l("main.old_pass_prompt"))); // Get the new password. Loop until the password is entered // correctly twice. do { // "New password:" newPassword = new String(cons.readPassword("%s ", ts.l("main.new_pass_prompt"))); // "Verify:" verifyPassword = new String(cons.readPassword("%s ", ts.l("main.verify_prompt"))); if (verifyPassword.equals(newPassword)) { break; } // "Passwords do not match. Try again." out.println(ts.l("main.no_match")); } while (true); // Now change the password with the passwordClient. boolean success = client.changePassword(argv[1], oldPassword, newPassword); if (success) { // "Successfully changed password." out.println(ts.l("main.success")); } else { // "Password change failed." out.println(ts.l("main.fail")); } } finally { System.exit(0); } } /** * <p>Load the properties file that we use to identify the Ganymede * server and port for the command line client.</p> */ private static boolean loadProperties(String filename) { Properties props = new Properties(); boolean success = true; String serverhost; /* -- */ BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(filename)); props.load(bis); } catch (IOException ex) { return false; } finally { if (bis != null) { try { bis.close(); } catch (IOException e) { } } } serverhost = props.getProperty("ganymede.serverhost"); int registryPortProperty = 1099; String registryPort = props.getProperty("ganymede.registryPort"); if (registryPort != null) { try { registryPortProperty = java.lang.Integer.parseInt(registryPort); } catch (NumberFormatException ex) { // Couldn''t get a valid registry port number from the ganymede.properties file: {0} System.err.println(ts.l("loadProperties.bad_port_property", registryPort)); } } if (serverhost == null) { // "Couldn''t get the server host property." System.err.println(ts.l("loadProperties.bad_host_property")); success = false; } else { url = "rmi://" + serverhost + ":" + registryPortProperty + "/ganymede.server"; } return success; } }