// ========================================================================
// Copyright (c) 1998 Mort Bay Consulting (Australia) Pty. Ltd.
// $Id: Password.java,v 1.1 2001/09/02 01:13:08 gregwilkins Exp $
// ========================================================================

package org.mortbay.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.zip.Adler32;
import java.util.zip.CRC32;

/* ------------------------------------------------------------ */
/** Password utility class.
 *
 * This utility class gets a password or pass phrase either by:<PRE>
 *  + Password is set as a system property.
 *  + The password is prompted for and read from standard input
 *  + A program is run to get the password.
 * </pre>
 * Passwords that begin with EXEC: are interpreted as a command, whose
 * output is read.
 * <p>
 * Passwords that begin with OBF: are de obfuscated.
 * Passwords can be obfuscated by run org.mortbay.util.Password as a
 * main class.  Obfuscated password are required if a system needs
 * to recover the full password (eg. so that it may be passed to another
 * system). They are not secure, but prevent casual observation.
 * <p>
 * Passwords that begin with CHK: are password checksums.  The real
 * password cannot be retrieved, but comparisons can be made to other
 * passwords.  A password checksum can be generated by running
 * org.mortbay.util.Password as a main class. Checksum passwords are
 * a secure way to stor passwords that only need to be checked rather
 * than recovered.
 *
 * Passwords that begin with CRYPT: are oneway encrypted with
 * UnixCrypt. The real password cannot be retrieved, but comparisons
 * can be made to other passwords.  A Crype can be generated by running
 * org.mortbay.util.UnixCrypt as a main class, passing password and
 * then the username. Checksum passwords are a secure(ish) way to
 * store passwords that only need to be checked rather
 * than recovered.  Note that it is not strong security - specially if
 * simple passwords are used.
 * 
 * @version $Id: Password.java,v 1.1 2001/09/02 01:13:08 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class Password
{
    private String _pw;
    private char[] _pwc;
    private String _cs;
    
    /* ------------------------------------------------------------ */
    public Password()
    {}
    
    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param realm  
     */
    public Password(String realm)
    {
        this(realm,"",null);
    }
    
    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param realm 
     */
    public Password(String realm,String passwd)
    {
        setPassword(realm,passwd);
    }
    
    /* ------------------------------------------------------------ */
    /** Constructor.
     * Attempts to get the password from a system property using
     * the realm name as a property key. If no password is found,
     * the default is used.  If the default is not set then stdin
     * is prompted for the password.  Then if no password is
     * entered, the promptDft is used.
     * @param realm The realm name
     * @param dft Default password, if none set in the realm
     * @param promptDft Default password, if prompt fails
     */
    public Password(String realm,String dft,String promptDft)
    {
        String passwd=System.getProperty(realm,dft);
        if (passwd==null || passwd.length()==0)
        {
            try
            {
                System.out.print(realm+
                                 ((promptDft!=null && promptDft.length()>0)
                                  ?" [dft]":"")+" : ");
                System.out.flush();
                byte[] buf = new byte[512];
                int len=System.in.read(buf);
                passwd=new String(buf,0,len).trim();
            }
            catch(IOException e)
            {
                Code.warning(e);
            }
            if (passwd==null || passwd.length()==0)
                passwd=promptDft;
        }
        setPassword(realm,passwd);
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param password 
     */
    private void setPassword(String realm,String password)
    {
        _pw=password;
        
        // expand password
        while (_pw!=null && _pw.startsWith("EXEC:"))
            _pw=expand(realm,_pw.substring(5).trim());

        while (_pw!=null && _pw.startsWith("OBF:"))
            _pw=deobfuscate(_pw);

        if (_pw.startsWith("CHK:"))
        {
            _cs=_pw;
            _pw=null;
            _pwc=null;
        }
        else if (_pw.startsWith("CRYPT:"))
        {
            _cs=_pw;
            _pw=null;
            _pwc=null;
        }
        else
        {
            _pwc = _pw.toCharArray();
            _cs=checksum(_pw);
        }
    }    

    /* ------------------------------------------------------------ */
    /* Evaluate exec password
     * @param realm 
     * @param pass 
     * @return 
     */
    private String expand(String realm, String pass)
    {
        Process process=null;
        try{
            process = Runtime.getRuntime().exec(pass);
            OutputStream out = process.getOutputStream();
            out.write((realm+"\n").getBytes());
            out.flush();
            InputStream in = process.getInputStream();
            byte[] buf = new byte[512];
            int len=in.read(buf);
            pass=new String(buf,0,len).trim();
        }
        catch(Exception e)
        {
            Code.warning(e);
        }
        finally
        {
            if (process!=null)
                process.destroy();
        }
        System.err.println("PW="+pass);
        return pass;
    }
    
    /* ------------------------------------------------------------ */
    public String toString()
    {
        return _pw==null?_cs:_pw;
    }
    
    /* ------------------------------------------------------------ */
    public String toStarString()
    {
        if (_pw==null)
            return null;
        return "********************************************************************************".substring(0,_pw.length());
    }

    /* ------------------------------------------------------------ */
    public char[] getCharArray()
    {
        if (_pwc==null)
            return null;
        return _pwc;
    }

    /* ------------------------------------------------------------ */
    public String getChecksum()
    {
        return _cs;
    }

    /* ------------------------------------------------------------ */
    public boolean check(String passwd)
    {
        Password pw=new Password("password",passwd);
        if (_pw!=null && pw._pw!=null)
            return _pw.equals(pw._pw);
        
        if (_cs.startsWith("CRYPT:"))
            return _cs.endsWith(UnixCrypt.crypt(passwd,_cs.substring("CRYPT:".length())));
                
        return getChecksum().equals(pw.getChecksum());
    }
    
    /* ------------------------------------------------------------ */
    public boolean equals(Object o)
    {
        Password pw=null;
        if (o instanceof Password)
        {
            pw=(Password)o;
            if (_pw!=null)
                return _pw.equals(pw._pw);
            if (_cs!=null && pw._pw==null)
                return _cs.equals(pw._cs);
        }
        return false;
    }
    
    /* ------------------------------------------------------------ */
    /** Zero the password.
     * The checksum is not effected, so comparisons may be made on
     * zeroed passwords.
     */
    public void zero()
    {
        _pw=null;
        if (_pwc!=null)
            java.util.Arrays.fill(_pwc,'\0');
        _pwc=null;
    }

    /* ------------------------------------------------------------ */
    public static String obfuscate(String s)
    {
        StringBuffer buf = new StringBuffer();
        byte[] b = s.getBytes();
        
        synchronized(buf)
        {
            buf.append("OBF:");
            for (int i=0;i<b.length;i++)
            {
                byte b1 = b[i];
                byte b2 = b[s.length()-(i+1)];
                int i1= (int)b1+(int)b2+127;
                int i2= (int)b1-(int)b2+127;
                int i0=i1*256+i2;
                String x=Integer.toString(i0,36);

                switch(x.length())
                {
                  case 1:buf.append('0');
                  case 2:buf.append('0');
                  case 3:buf.append('0');
                  default:buf.append(x);
                }
            }
            return buf.toString();
        }
    }
    
    /* ------------------------------------------------------------ */
    public static String deobfuscate(String s)
    {
        if (s.startsWith("OBF:"))
            s=s.substring(4);
        
        byte[] b=new byte[s.length()/2];
        int l=0;
        for (int i=0;i<s.length();i+=4)
        {
            String x=s.substring(i,i+4);
            int i0 = Integer.parseInt(x,36);
            int i1=(i0/256);
            int i2=(i0%256);
            b[l++]=(byte)((i1+i2-254)/2);
        }

        return new String(b,0,l);
    }

    /* ------------------------------------------------------------ */
    public static String checksum(String s)
    {
        byte[] b=null;
        try{b=s.getBytes(StringUtil.__ISO_8859_1);}
        catch(UnsupportedEncodingException e){Code.fail(e);}
        
        CRC32 crc32 = new CRC32();
        Adler32 adler32 = new Adler32();
        crc32.update(b);
        adler32.update(b);
        StringBuffer buf=new StringBuffer();
        synchronized(buf)
        {
            buf.append("CHK:");
            buf.append(Long.toString(crc32.getValue(),32));
            buf.append(Long.toString(adler32.getValue(),32));
            return buf.toString();
        }
    }
    
    /* ------------------------------------------------------------ */
    public static String crypt(String user, String pw)
    {
        return "CRYPT:"+UnixCrypt.crypt(pw,user);
    }
    
    /* ------------------------------------------------------------ */
    /** 
     * @param arg 
     */
    public static void main(String[] arg)
    {
        if (arg.length!=1 && arg.length!=2 )
        {
            System.err.println("Usage - java c.m.U.Password [<user>] <password>");
            System.err.println("If the password is ?, the user will be prompted for the password");
            System.exit(1);
        }
        String p=arg[arg.length==1?0:1];
        Password pw = "?".equals(p)?new Password("password"):new Password("password",p);
        System.err.println(pw.toString());
        System.err.println(obfuscate(pw.toString()));
        System.err.println(checksum(pw.toString()));
        if (arg.length==2)
            System.err.println(crypt(arg[0],pw.toString()));
    }    
}


