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 }