============ Introduction ============ Change password listener provides a mechanism that allows extensions to execute customized tasks before and/or after the password is changed in the Zimbra LDAP server. See bug 17321. =================================================== Preparing a domain or global config for custom auth =================================================== Change password listener can be configured on domains or on global config, by setting the domain/global config attribute zimbraPasswordChangeListener to the name of the listener. For example: zmprov modifyDomain example.com zimbraPasswordChangeListener samba or zmprov modifyConfig zimbraPasswordChangeListener samba In the above examples, "samba" is the name under which a change password listener is registered. It must be registered by invoking ChangePasswordListener.register(...). ====================================== Registering a change password listener ====================================== Invoke ChangePasswordListener.register(listenerName, listener) in the init method of the extension. Class: com.zimbra.cs.account.ldap.ChangePasswordListener Method: public synchronized static void register(String listenerName, ChangePasswordListener listener) { listenerName: Name under which this listener is registered. This is the name that needs to be set in the zimbraPasswordChangeListener attribute of the domain or global config. handler: The object on which the preModify and postModify methods will be invoked for this listener. It has to be an instance of a subclass of ChangePasswordListener. For example: class SambaChangePasswordListener extends ChangePasswordListener { ... } public class SambaExtension implements ZimbraExtension { public void init() throws ServiceException { ChangePasswordListener.register("samba", new SambaChangePasswordListener()); } ... } ======================================================= Implementing abstract methods of ChangePasswordListener ======================================================= Two abstract methods: preModify and postModify of ChangePasswordListener must be implemented in the class that the registered listener object is an instance of. /** * Called before password(userPassword) and applicable(e.g. zimbraPasswordHistory, zimbraPasswordModifiedTime) * attributes are modified in LDAP. If a ServiceException is thrown, no attributes will be modified. * * The attrsToModify map should not be modified, other then for adding attributes defined in * a LDAP schema extension. * * @param account account object being modified * @param newPassword Clear-text new password * @param context place to stash data between invocations of pre/postModify * @param attrsToModify a map of all the attributes being modified * @return Returning from this function indicating preModify has succeeded. * @throws Exception. If an Exception is thrown, no attributes will be modified. */ public abstract void preModify(Account acct, String newPassword, Map context, Map attrsToModify) throws ServiceException; /** * called after a successful modify of the attributes. should not throw any exceptions. * * @param account account object being modified * @param newPassword Clear-text new password * @param context place to stash data between invocations of pre/postModify */ public abstract void postModify(Account acct, String newPassword, Map context); For example: class SambaChangePasswordListener extends ChangePasswordListener { public void preModify(Account acct, String newPassword, Map context, Map attrsToModify) throws ServiceException { String lmPassword = ""; try { Process p1 = Runtime.getRuntime().exec("/opt/zimbra/bin/mkntpwd -L " + newPassword); BufferedReader bf1 = new BufferedReader(new InputStreamReader(p1.getInputStream())); lmPassword = bf1.readLine(); } catch (IOException ioe) { ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] {"cmd", "LdapProvisioning.SetPassword","lmPassword", ioe.getMessage()})); } String ntPassword = ""; try { Process p2 = Runtime.getRuntime().exec("/opt/zimbra/bin/mkntpwd -N " + newPassword); BufferedReader bf2=new BufferedReader(new InputStreamReader(p2.getInputStream())); ntPassword = bf2.readLine(); } catch (IOException ioe) { ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] {"cmd", "LdapProvisioning.SetPassword","ntPassword", ioe.getMessage()})); } attrsToModify.put("sambaLMPassword", lmPassword); attrsToModify.put("sambaNTPassword", ntPassword); ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] {"cmd", "LdapProvisioning.SetPassword","lmPassword", lmPassword})); ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] {"cmd", "LdapProvisioning.SetPassword","ntPassword", ntPassword})); } public void postModify(Account acct, String newPassword, Map context) { // do nothing } }