/* PasswordAgingTask.java This task is run periodically to check all user accounts in Ganymede for their password expiration time. Created: 14 June 2001 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2012 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.gasharl; import java.rmi.RemoteException; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.Vector; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryDataNode; import arlut.csd.ganymede.common.Result; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.server.DBObject; import arlut.csd.ganymede.server.DBLog; import arlut.csd.ganymede.server.Ganymede; import arlut.csd.ganymede.server.GanymedeServer; import arlut.csd.ganymede.server.GanymedeSession; /*------------------------------------------------------------------------------ class PasswordAgingTask ------------------------------------------------------------------------------*/ /** * * This task is run periodically to check all user accounts in Ganymede * for their password expiration time. * * * @author Jonathan Abbey jonabbey@arlut.utexas.edu */ public class PasswordAgingTask implements Runnable { static final boolean debug = true; static final public String name = "password aging task"; /* -- */ GanymedeSession mySession = null; Thread currentThread = null; /** * * Just Do It (tm) * * @see java.lang.Runnable * */ public void run() { boolean transactionOpen = false; /* -- */ currentThread = java.lang.Thread.currentThread(); Ganymede.debug("Password Aging Task: Starting"); String error = GanymedeServer.checkEnabled(); if (error != null) { Ganymede.debug("Deferring password aging task - semaphore disabled: " + error); return; } try { try { mySession = new GanymedeSession("PasswordAgingTask"); } catch (RemoteException ex) { Ganymede.debug("PasswordAging Task: Couldn't establish session"); return; } // we don't want no wizards mySession.enableWizards(false); // and we don't want forced required fields oversight.. this // can leave us with some objects which fail a high level // consistency check, but we can do a query to scan for them // later mySession.enableOversight(false); ReturnVal retVal = mySession.openTransaction(PasswordAgingTask.name); if (retVal != null && !retVal.didSucceed()) { Ganymede.debug("PasswordAging Task: Couldn't open transaction"); return; } transactionOpen = true; // do the stuff handlePasswords(); retVal = mySession.commitTransaction(); if (retVal != null && !retVal.didSucceed()) { // if doNormalProcessing is true, the // transaction was not cleared, but was // left open for a re-try. Abort it. if (retVal.doNormalProcessing) { Ganymede.debug("PasswordAging Task: couldn't fully commit, trying to abort."); mySession.abortTransaction(); } Ganymede.debug("PasswordAging Task: Couldn't successfully commit transaction"); } else { Ganymede.debug("PasswordAging Task: Transaction committed"); } transactionOpen = false; } catch (InterruptedException ex) { } catch (NotLoggedInException ex) { } finally { if (transactionOpen) { Ganymede.debug("PasswordAging Task: Forced to terminate early, aborting transaction"); } mySession.logout(); } } private void handlePasswords() throws InterruptedException, NotLoggedInException { Query q; Vector results; Result result; Invid invid; DBObject object; Date currentTime = new Date(); Calendar lowerBound = new GregorianCalendar(); Calendar upperBound = new GregorianCalendar(); Enumeration en; QueryDataNode matchNode = new QueryDataNode(userSchema.PASSWORDCHANGETIME, QueryDataNode.DEFINED, null); /* -- */ q = new Query(SchemaConstants.UserBase, matchNode, false); results = mySession.internalQuery(q); en = results.elements(); while (en.hasMoreElements()) { if (currentThread.isInterrupted()) { throw new InterruptedException("scheduler ordering shutdown"); } result = (Result) en.nextElement(); invid = result.getInvid(); object = mySession.getDBSession().viewDBObject(invid); if (object == null || object.isInactivated()) { continue; } // get the password expiration threshold for this user, if any Date passwordTime = (Date) object.getFieldValueLocal(userSchema.PASSWORDCHANGETIME); if (passwordTime == null) { continue; } // first, check for one month into the future // note: we used to try and do month add, but we can't do this // cleanly if the next month has fewer days than this month // does, i.e., if someone's password expires on February 28th // and it is January 29th, Java considers January 29th + 1 // month to be February 28th. Ditto January 30th + 1 month, // ditto January 31st + 1 month. // // so, we just go for four weeks of notice lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, 27); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, 28); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnUpcomingPasswordExpire(object); continue; } // then two weeks // // likewise, Java's week calculations for WEEK_OF_MONTH, etc., // are very squirrely, so we'll just stick to simple date // incrementing. lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, 13); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, 14); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnUpcomingPasswordExpire(object); continue; } // then one week lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, 6); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, 7); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnUpcomingPasswordExpire(object); continue; } // then one day before lowerBound.setTime(currentTime); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, 1); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnRealSoonNowPasswordExpire(object); continue; } // then day one of the grace period lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, -1); upperBound.setTime(currentTime); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnOvertimePassword(object); continue; } // then day two of the grace period lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, -2); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, -1); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnOvertimePassword(object); continue; } // then day three of the grace period lowerBound.setTime(currentTime); lowerBound.add(Calendar.DATE, -3); upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, -2); if (passwordTime.after(lowerBound.getTime()) && passwordTime.before(upperBound.getTime())) { warnOvertimePassword(object); continue; } // then we blow it away upperBound.setTime(currentTime); upperBound.add(Calendar.DATE, -3); if (passwordTime.before(upperBound.getTime())) { ReturnVal retVal = mySession.inactivate_db_object(invid); if (retVal != null && !retVal.didSucceed()) { Ganymede.debug("PasswordAging task was not able to inactivate user " + result.toString()); } else { Ganymede.debug("PasswordAging task inactivated user " + result.toString()); } } } } /** * <p>Send out a warning that the password is going to expire, to the user's * email addresses only.</p> */ private void warnUpcomingPasswordExpire(DBObject userObject) { Date passwordChangeTime = (Date) userObject.getFieldValueLocal(userSchema.PASSWORDCHANGETIME); Vector objVect = new Vector(); objVect.addElement(userObject.getInvid()); String titleString = "Password Expiring Soon For User " + userObject.getLabel() + ", at " + passwordChangeTime; String messageString = "The password for user account " + userObject.getLabel() + " will expire soon. You will need to change your password before " + passwordChangeTime + " or else your user account will be inactivated.\n\n" + "You can change your password online by visiting https://www.arlut.utexas.edu/password/\n\n" + "If you need assistance with this matter, please contact one of your lab unit's Ganymede administrators."; Ganymede.log.sendMail(null, titleString, messageString, DBLog.MailMode.USERS, objVect); } /** * <p>Send out a warning that the password is going to expire soon, to the user's * and admin groups' email addresses.</p> */ private void warnRealSoonNowPasswordExpire(DBObject userObject) { Date passwordChangeTime = (Date) userObject.getFieldValueLocal(userSchema.PASSWORDCHANGETIME); Vector objVect = new Vector(); objVect.addElement(userObject.getInvid()); String titleString = "Password Expiring Very Soon For User " + userObject.getLabel() + ", at " + passwordChangeTime; String messageString = "The password for user account " + userObject.getLabel() + " will expire very soon. The password for this user account will need to be changed before " + passwordChangeTime + " or else the account will be inactivated.\n\n" + "You can change your password online by visiting https://www.arlut.utexas.edu/password/\n\n" + "If you need assistance with this matter, please contact one of your lab unit's Ganymede administrators, " + "or CSD."; Ganymede.log.sendMail(null, titleString, messageString, DBLog.MailMode.BOTH, objVect); } /** * <p>Send out a very urgent warning that the password is past its * expiration date, to the user's and admin groups' email * addresses.</p> */ private void warnOvertimePassword(DBObject userObject) { Date passwordChangeTime = (Date) userObject.getFieldValueLocal(userSchema.PASSWORDCHANGETIME); Vector objVect = new Vector(); objVect.addElement(userObject.getInvid()); String titleString = "Password Has Expired For User " + userObject.getLabel() + "!!!"; String messageString = "The password for user account " + userObject.getLabel() + " expired at " + passwordChangeTime + ".\n\nThe password for this user account *must* be changed immediately, or else" + " the account will be inactivated. If this account is inactivated, extension of the password" + " expiration deadline will be impossible, and a new password will need to be chosen to re-enable" + " this account.\n\n" + "You can change your password online by visiting https://www.arlut.utexas.edu/password/\n\n" + "If you need assistance with this matter, please contact one of your lab unit's Ganymede administrators, " + "or CSD."; Ganymede.log.sendMail(null, titleString, messageString, DBLog.MailMode.BOTH, objVect); } }