    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.imap;
    019
    020    import java.io.IOException;
    021    import java.security.InvalidKeyException;
    022    import java.security.NoSuchAlgorithmException;
    023    import java.security.spec.InvalidKeySpecException;
    024
    025    import javax.crypto.Mac;
    026    import javax.crypto.spec.SecretKeySpec;
    027    import javax.net.ssl.SSLContext;
    028    import org.apache.commons.net.util.Base64;
    029
    030    /**
    031     * An IMAP Client class with authentication support.
    032     * @see IMAPSClient
    033     */
    034    public class AuthenticatingIMAPClient extends IMAPSClient
    035    {
    036        /**
    037         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    038         * Sets security mode to explicit (isImplicit = false).
    039         */
    040        public AuthenticatingIMAPClient()
    041        {
    042            this(DEFAULT_PROTOCOL, false);
    043        }
    044
    045        /**
    046         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    047         * @param implicit The security mode (Implicit/Explicit).
    048         */
    049        public AuthenticatingIMAPClient(boolean implicit)
    050        {
    051            this(DEFAULT_PROTOCOL, implicit);
    052        }
    053
    054        /**
    055         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    056         * @param proto the protocol.
    057         */
    058        public AuthenticatingIMAPClient(String proto)
    059        {
    060            this(proto, false);
    061        }
    062
    063        /**
    064         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    065         * @param proto the protocol.
    066         * @param implicit The security mode(Implicit/Explicit).
    067         */
    068        public AuthenticatingIMAPClient(String proto, boolean implicit)
    069        {
    070            this(proto, implicit, null);
    071        }
    072
    073        /**
    074         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    075         * @param proto the protocol.
    076         * @param implicit The security mode(Implicit/Explicit).
    077         */
    078        public AuthenticatingIMAPClient(String proto, boolean implicit, SSLContext ctx)
    079        {
    080            super(proto, implicit, ctx);
    081        }
    082
    083        /**
    084         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    085         * @param implicit The security mode(Implicit/Explicit).
    086         * @param ctx A pre-configured SSL Context.
    087         */
    088        public AuthenticatingIMAPClient(boolean implicit, SSLContext ctx)
    089        {
    090            this(DEFAULT_PROTOCOL, implicit, ctx);
    091        }
    092
    093        /**
    094         * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient.
    095         * @param context A pre-configured SSL Context.
    096         */
    097        public AuthenticatingIMAPClient(SSLContext context)
    098        {
    099            this(false, context);
    100        }
    101
    102        /**
    103         * Authenticate to the IMAP server by sending the AUTHENTICATE command with the
    104         * selected mechanism, using the given username and the given password.
    105         * <p>
    106         * @return True if successfully completed, false if not.
    107         * @exception IOException  If an I/O error occurs while either sending a
    108         *      command to the server or receiving a reply from the server.
    109         * @exception NoSuchAlgorithmException If the CRAM hash algorithm
    110         *      cannot be instantiated by the Java runtime system.
    111         * @exception InvalidKeyException If the CRAM hash algorithm
    112         *      failed to use the given password.
    113         * @exception InvalidKeySpecException If the CRAM hash algorithm
    114         *      failed to use the given password.
    115         */
    116        public boolean authenticate(AuthenticatingIMAPClient.AUTH_METHOD method,
    117                            String username, String password)
    118                            throws IOException, NoSuchAlgorithmException,
    119                            InvalidKeyException, InvalidKeySpecException
    120        {
    121            return auth(method, username, password);
    122        }
    123
    124        /**
    125         * Authenticate to the IMAP server by sending the AUTHENTICATE command with the
    126         * selected mechanism, using the given username and the given password.
    127         * <p>
    128         * @return True if successfully completed, false if not.
    129         * @exception IOException  If an I/O error occurs while either sending a
    130         *      command to the server or receiving a reply from the server.
    131         * @exception NoSuchAlgorithmException If the CRAM hash algorithm
    132         *      cannot be instantiated by the Java runtime system.
    133         * @exception InvalidKeyException If the CRAM hash algorithm
    134         *      failed to use the given password.
    135         * @exception InvalidKeySpecException If the CRAM hash algorithm
    136         *      failed to use the given password.
    137         */
    138        public boolean auth(AuthenticatingIMAPClient.AUTH_METHOD method,
    139                            String username, String password)
    140                            throws IOException, NoSuchAlgorithmException,
    141                            InvalidKeyException, InvalidKeySpecException
    142        {
    143            if (!IMAPReply.isContinuation(sendCommand(IMAPCommand.AUTHENTICATE, method.getAuthName())))
    144            {
    145                return false;
    146            }
    147
    148            switch (method) {
    149                case PLAIN:
    150                {
    151                    // the server sends an empty response ("+ "), so we don't have to read it.
    152                    int result = sendData(
    153                        Base64.encodeBase64StringUnChunked(("\000" + username + "\000" + password).getBytes())
    154                        );
    155                    if (result == IMAPReply.OK)
    156                    {
    157                        setState(IMAP.IMAPState.AUTH_STATE);
    158                    }
    159                    return result == IMAPReply.OK;
    160                }
    161                case CRAM_MD5:
    162                {
    163                    // get the CRAM challenge (after "+ ")
    164                    byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim());
    165                    // get the Mac instance
    166                    Mac hmac_md5 = Mac.getInstance("HmacMD5");
    167                    hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5"));
    168                    // compute the result:
    169                    byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes();
    170                    // join the byte arrays to form the reply
    171                    byte[] usernameBytes = username.getBytes();
    172                    byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length];
    173                    System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length);
    174                    toEncode[usernameBytes.length] = ' ';
    175                    System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length);
    176                    // send the reply and read the server code:
    177                    int result = sendData(Base64.encodeBase64StringUnChunked(toEncode));
    178                    if (result == IMAPReply.OK)
    179                    {
    180                        setState(IMAP.IMAPState.AUTH_STATE);
    181                    }
    182                    return result == IMAPReply.OK;
    183                }
    184                case LOGIN:
    185                {
    186                    // the server sends fixed responses (base64("Username") and
    187                    // base64("Password")), so we don't have to read them.
    188                    if (sendData(
    189                        Base64.encodeBase64StringUnChunked(username.getBytes())) != IMAPReply.CONT)
    190                    {
    191                        return false;
    192                    }
    193                    int result = sendData(Base64.encodeBase64StringUnChunked(password.getBytes()));
    194                    if (result == IMAPReply.OK)
    195                    {
    196                        setState(IMAP.IMAPState.AUTH_STATE);
    197                    }
    198                    return result == IMAPReply.OK;
    199                }
    200                case XOAUTH:
    201                {
    202                    int result = sendData(new String(username.getBytes()));
    203                    if (result == IMAPReply.OK)
    204                    {
    205                        setState(IMAP.IMAPState.AUTH_STATE);
    206                    }
    207                    return result == IMAPReply.OK;
    208                }
    209            }
    210            return false; // safety check
    211        }
    212
    213        /**
    214         * Converts the given byte array to a String containing the hex values of the bytes.
    215         * For example, the byte 'A' will be converted to '41', because this is the ASCII code
    216         * (and the byte value) of the capital letter 'A'.
    217         * @param a The byte array to convert.
    218         * @return The resulting String of hex codes.
    219         */
    220        private String _convertToHexString(byte[] a)
    221        {
    222            StringBuilder result = new StringBuilder(a.length*2);
    223            for (byte element : a)
    224            {
    225                if ( (element & 0x0FF) <= 15 ) {
    226                    result.append("0");
    227                }
    228                result.append(Integer.toHexString(element & 0x0FF));
    229            }
    230            return result.toString();
    231        }
    232
    233        /**
    234         * The enumeration of currently-supported authentication methods.
    235         */
    236        public static enum AUTH_METHOD
    237        {
    238            /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */
    239            PLAIN("PLAIN"),
    240            /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
    241            CRAM_MD5("CRAM-MD5"),
    242            /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */
    243            LOGIN("LOGIN"),
    244            /** XOAUTH */
    245            XOAUTH("XOAUTH");
    246
    247            private final String authName;
    248
    249            private AUTH_METHOD(String name){
    250                this.authName=name;
    251            }
    252            /**
    253             * Gets the name of the given authentication method suitable for the server.
    254             * @return The name of the given authentication method suitable for the server.
    255             */
    256            public final String getAuthName()
    257            {
    258                return authName;
    259            }
    260        }
    261    }
    262    /* kate: indent-width 4; replace-tabs on; */