ExtendedPOP3Client.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.pop3;

  18. import java.io.IOException;
  19. import java.security.InvalidKeyException;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.security.spec.InvalidKeySpecException;
  22. import java.util.Base64;

  23. import javax.crypto.Mac;
  24. import javax.crypto.spec.SecretKeySpec;

  25. /**
  26.  * A POP3 Cilent class with protocol and authentication extensions support (RFC2449 and RFC2195).
  27.  *
  28.  * @see POP3Client
  29.  * @since 3.0
  30.  */
  31. public class ExtendedPOP3Client extends POP3SClient {

  32.     /**
  33.      * The enumeration of currently-supported authentication methods.
  34.      */
  35.     public enum AUTH_METHOD {

  36.         /** The standardized (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */
  37.         PLAIN("PLAIN"),

  38.         /** The standardized (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
  39.         CRAM_MD5("CRAM-MD5");

  40.         private final String methodName;

  41.         AUTH_METHOD(final String methodName) {
  42.             this.methodName = methodName;
  43.         }

  44.         /**
  45.          * Gets the name of the given authentication method suitable for the server.
  46.          *
  47.          * @return The name of the given authentication method suitable for the server.
  48.          */
  49.         public final String getAuthName() {
  50.             return this.methodName;
  51.         }
  52.     }

  53.     /** {@link Mac} algorithm. */
  54.     private static final String MAC_ALGORITHM = "HmacMD5";

  55.     /**
  56.      * The default ExtendedPOP3Client constructor. Creates a new Extended POP3 Client.
  57.      *
  58.      * @throws NoSuchAlgorithmException on error
  59.      */
  60.     public ExtendedPOP3Client() throws NoSuchAlgorithmException {
  61.     }

  62.     /**
  63.      * Authenticate to the POP3 server by sending the AUTH command with the selected mechanism, using the given user and the given password.
  64.      *
  65.      * @param method   the {@link AUTH_METHOD} to use
  66.      * @param user the user name
  67.      * @param password the password
  68.      * @return True if successfully completed, false if not.
  69.      * @throws IOException              If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  70.      * @throws NoSuchAlgorithmException If the CRAM hash algorithm cannot be instantiated by the Java runtime system.
  71.      * @throws InvalidKeyException      If the CRAM hash algorithm failed to use the given password.
  72.      * @throws InvalidKeySpecException  If the CRAM hash algorithm failed to use the given password.
  73.      */
  74.     public boolean auth(final AUTH_METHOD method, final String user, final String password)
  75.             throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
  76.         if (sendCommand(POP3Command.AUTH, method.getAuthName()) != POP3Reply.OK_INT) {
  77.             return false;
  78.         }

  79.         switch (method) {
  80.         case PLAIN:
  81.             // the server sends an empty response ("+ "), so we don't have to read it.
  82.             return sendCommand(
  83.                     new String(Base64.getEncoder().encode(("\000" + user + "\000" + password).getBytes(getCharset())), getCharset())) == POP3Reply.OK;
  84.         case CRAM_MD5:
  85.             // get the CRAM challenge
  86.             final byte[] serverChallenge = Base64.getDecoder().decode(getReplyString().substring(2).trim());
  87.             // get the Mac instance
  88.             final Mac hmacMd5 = Mac.getInstance(MAC_ALGORITHM);
  89.             hmacMd5.init(new SecretKeySpec(password.getBytes(getCharset()), MAC_ALGORITHM));
  90.             // compute the result:
  91.             final byte[] hmacResult = convertToHexString(hmacMd5.doFinal(serverChallenge)).getBytes(getCharset());
  92.             // join the byte arrays to form the reply
  93.             final byte[] userNameBytes = user.getBytes(getCharset());
  94.             final byte[] toEncode = new byte[userNameBytes.length + 1 /* the space */ + hmacResult.length];
  95.             System.arraycopy(userNameBytes, 0, toEncode, 0, userNameBytes.length);
  96.             toEncode[userNameBytes.length] = ' ';
  97.             System.arraycopy(hmacResult, 0, toEncode, userNameBytes.length + 1, hmacResult.length);
  98.             // send the reply and read the server code:
  99.             return sendCommand(Base64.getEncoder().encodeToString(toEncode)) == POP3Reply.OK;
  100.         default:
  101.             return false;
  102.         }
  103.     }

  104.     /**
  105.      * Converts the given byte array to a String containing the hexadecimal values of the bytes. For example, the byte 'A' will be converted to '41', because
  106.      * this is the ASCII code (and the byte value) of the capital letter 'A'.
  107.      *
  108.      * @param a The byte array to convert.
  109.      * @return The resulting String of hexadecimal codes.
  110.      */
  111.     private String convertToHexString(final byte[] a) {
  112.         final StringBuilder result = new StringBuilder(a.length * 2);
  113.         for (final byte element : a) {
  114.             if ((element & 0x0FF) <= 15) {
  115.                 result.append("0");
  116.             }
  117.             result.append(Integer.toHexString(element & 0x0FF));
  118.         }
  119.         return result.toString();
  120.     }
  121. }