============
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<String, Object> 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<String, Object> 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
        }
    }     
   