    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.util.Base64;
    028
    029
    030    /**
    031     * A POP3 Cilent class with protocol and authentication extensions support
    032     * (RFC2449 and RFC2195).
    033     * @see POP3Client
    034     * @since 3.0
    035     */
    036    public class ExtendedPOP3Client extends POP3SClient
    037    {
    038        /**
    039         * The default ExtendedPOP3Client constructor.
    040         * Creates a new Extended POP3 Client.
    041         * @throws NoSuchAlgorithmException
    042         */
    043        public ExtendedPOP3Client() throws NoSuchAlgorithmException
    044        {
    045            super();
    046        }
    047
    048        /***
    049         * Authenticate to the POP3 server by sending the AUTH command with the
    050         * selected mechanism, using the given username and the given password.
    051         * <p>
    052         * @param method the {@link AUTH_METHOD} to use
    053         * @param username the user name
    054         * @param password the password
    055         * @return True if successfully completed, false if not.
    056         * @exception IOException  If an I/O error occurs while either sending a
    057         *      command to the server or receiving a reply from the server.
    058         * @exception NoSuchAlgorithmException If the CRAM hash algorithm
    059         *      cannot be instantiated by the Java runtime system.
    060         * @exception InvalidKeyException If the CRAM hash algorithm
    061         *      failed to use the given password.
    062         * @exception InvalidKeySpecException If the CRAM hash algorithm
    063         *      failed to use the given password.
    064         ***/
    065        public boolean auth(AUTH_METHOD method,
    066                            String username, String password)
    067                            throws IOException, NoSuchAlgorithmException,
    068                            InvalidKeyException, InvalidKeySpecException
    069        {
    070            if (sendCommand(POP3Command.AUTH, method.getAuthName())
    071            != POP3Reply.OK_INT) {
    072                return false;
    073            }
    074
    075            switch(method) {
    076                case PLAIN:
    077                    // the server sends an empty response ("+ "), so we don't have to read it.
    078                    return sendCommand(
    079                        new String(
    080                            Base64.encodeBase64(("\000" + username + "\000" + password).getBytes())
    081                            )
    082                        ) == POP3Reply.OK;
    083                case CRAM_MD5:
    084                    // get the CRAM challenge
    085                    byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim());
    086                    // get the Mac instance
    087                    Mac hmac_md5 = Mac.getInstance("HmacMD5");
    088                    hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5"));
    089                    // compute the result:
    090                    byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes();
    091                    // join the byte arrays to form the reply
    092                    byte[] usernameBytes = username.getBytes();
    093                    byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length];
    094                    System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length);
    095                    toEncode[usernameBytes.length] = ' ';
    096                    System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length);
    097                    // send the reply and read the server code:
    098                    return sendCommand(Base64.encodeBase64StringUnChunked(toEncode)) == POP3Reply.OK;
    099                default:
    100                    return false;
    101            }
    102        }
    103
    104        /**
    105         * Converts the given byte array to a String containing the hex values of the bytes.
    106         * For example, the byte 'A' will be converted to '41', because this is the ASCII code
    107         * (and the byte value) of the capital letter 'A'.
    108         * @param a The byte array to convert.
    109         * @return The resulting String of hex codes.
    110         */
    111        private String _convertToHexString(byte[] a)
    112        {
    113            StringBuilder result = new StringBuilder(a.length*2);
    114            for (byte element : a)
    115            {
    116                if ( (element & 0x0FF) <= 15 ) {
    117                    result.append("0");
    118                }
    119                result.append(Integer.toHexString(element & 0x0FF));
    120            }
    121            return result.toString();
    122        }
    123
    124        /**
    125         * The enumeration of currently-supported authentication methods.
    126         */
    127        public static enum AUTH_METHOD
    128        {
    129            /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */
    130            PLAIN("PLAIN"),
    131
    132            /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
    133            CRAM_MD5("CRAM-MD5");
    134
    135            private final String methodName;
    136
    137            AUTH_METHOD(String methodName){
    138                this.methodName = methodName;
    139            }
    140            /**
    141             * Gets the name of the given authentication method suitable for the server.
    142             * @return The name of the given authentication method suitable for the server.
    143             */
    144            public final String getAuthName()
    145            {
    146                return this.methodName;
    147            }
    148        }
    149    }