    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.net.pop3;
    019
    020    import java.io.IOException;
    021    import java.security.InvalidKeyException;
    022    import java.security.NoSuchAlgorithmException;
    023    import java.security.spec.InvalidKeySpecException;
    024    import javax.crypto.Mac;
    025    import javax.crypto.spec.SecretKeySpec;
    026
    027    import org.apache.commons.net.pop3.POP3Client;
    028    import org.apache.commons.net.pop3.POP3Reply;
    029    import org.apache.commons.net.util.Base64;
    030
    031
    032    /**
    033     * A POP3 Cilent class with protocol and authentication extensions support
    034     * (RFC2449 and RFC2195).
    035     * @see POP3Client
    036     * @since 3.0
    037     */
    038    public class ExtendedPOP3Client extends POP3SClient
    039    {
    040        /**
    041         * The default ExtendedPOP3Client constructor.
    042         * Creates a new Extended POP3 Client.
    043         * @throws NoSuchAlgorithmException
    044         */
    045        public ExtendedPOP3Client() throws NoSuchAlgorithmException
    046        {
    047            super();
    048        }
    049
    050        /***
    051         * Send a CAPA command to the POP3 server.
    052         * @return True if the command was successful, false if not.
    053         * @exception IOException If a network I/O error occurs in the process of
    054         *        sending the NOOP command.
    055         ***/
    056        public boolean capa() throws IOException
    057        {
    058            return (sendCommand(POP3Command.CAPA) == POP3Reply.OK);
    059        }
    060
    061        /***
    062         * Authenticate to the POP3 server by sending the AUTH command with the
    063         * selected mechanism, using the given username and the given password.
    064         * <p>
    065         * @param method the {@link AUTH_METHOD} to use
    066         * @param username the user name
    067         * @param password the password
    068         * @return True if successfully completed, false if not.
    069         * @exception IOException  If an I/O error occurs while either sending a
    070         *      command to the server or receiving a reply from the server.
    071         * @exception NoSuchAlgorithmException If the CRAM hash algorithm
    072         *      cannot be instantiated by the Java runtime system.
    073         * @exception InvalidKeyException If the CRAM hash algorithm
    074         *      failed to use the given password.
    075         * @exception InvalidKeySpecException If the CRAM hash algorithm
    076         *      failed to use the given password.
    077         ***/
    078        public boolean auth(AUTH_METHOD method,
    079                            String username, String password)
    080                            throws IOException, NoSuchAlgorithmException,
    081                            InvalidKeyException, InvalidKeySpecException
    082        {
    083            if (sendCommand(POP3Command.AUTH, method.getAuthName())
    084            != POP3Reply.OK_INT) return false;
    085
    086            switch(method) {
    087                case PLAIN:
    088                    // the server sends an empty response ("+ "), so we don't have to read it.
    089                    return sendCommand(
    090                        new String(
    091                            Base64.encodeBase64(("\000" + username + "\000" + password).getBytes())
    092                            )
    093                        ) == POP3Reply.OK;
    094                case CRAM_MD5:
    095                    // get the CRAM challenge
    096                    byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim());
    097                    // get the Mac instance
    098                    Mac hmac_md5 = Mac.getInstance("HmacMD5");
    099                    hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5"));
    100                    // compute the result:
    101                    byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes();
    102                    // join the byte arrays to form the reply
    103                    byte[] usernameBytes = username.getBytes();
    104                    byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length];
    105                    System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length);
    106                    toEncode[usernameBytes.length] = ' ';
    107                    System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length);
    108                    // send the reply and read the server code:
    109                    return sendCommand(new String(Base64.encodeBase64(toEncode))) == POP3Reply.OK;
    110                default:
    111                    return false;
    112            }
    113        }
    114
    115        /**
    116         * Converts the given byte array to a String containing the hex values of the bytes.
    117         * For example, the byte 'A' will be converted to '41', because this is the ASCII code
    118         * (and the byte value) of the capital letter 'A'.
    119         * @param a The byte array to convert.
    120         * @return The resulting String of hex codes.
    121         */
    122        private String _convertToHexString(byte[] a)
    123        {
    124            StringBuilder result = new StringBuilder(a.length*2);
    125            for (int i = 0; i < a.length; i++)
    126            {
    127                if ( (a[i] & 0x0FF) <= 15 ) result.append("0");
    128                result.append(Integer.toHexString(a[i] & 0x0FF));
    129            }
    130            return result.toString();
    131        }
    132
    133        /**
    134         * The enumeration of currently-supported authentication methods.
    135         */
    136        public static enum AUTH_METHOD
    137        {
    138            /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */
    139            PLAIN("PLAIN"),
    140
    141            /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
    142            CRAM_MD5("CRAM-MD5");
    143
    144            private final String methodName;
    145
    146            AUTH_METHOD(String methodName){
    147                this.methodName = methodName;
    148            }
    149            /**
    150             * Gets the name of the given authentication method suitable for the server.
    151             * @return The name of the given authentication method suitable for the server.
    152             */
    153            public final String getAuthName()
    154            {
    155                return this.methodName;
    156            }
    157        }
    158    }