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 new String(
154 Base64.encodeBase64(("\000" + username + "\000" + password).getBytes())
155 )
156 );
157 if (result == IMAPReply.OK)
158 {
159 setState(IMAP.IMAPState.AUTH_STATE);
160 }
161 return result == IMAPReply.OK;
162 }
163 case CRAM_MD5:
164 {
165 // get the CRAM challenge (after "+ ")
166 byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim());
167 // get the Mac instance
168 Mac hmac_md5 = Mac.getInstance("HmacMD5");
169 hmac_md5.init(new SecretKeySpec(password.getBytes(), "HmacMD5"));
170 // compute the result:
171 byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes();
172 // join the byte arrays to form the reply
173 byte[] usernameBytes = username.getBytes();
174 byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length];
175 System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length);
176 toEncode[usernameBytes.length] = ' ';
177 System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length);
178 // send the reply and read the server code:
179 int result = sendData(new String(Base64.encodeBase64(toEncode)));
180 if (result == IMAPReply.OK)
181 {
182 setState(IMAP.IMAPState.AUTH_STATE);
183 }
184 return result == IMAPReply.OK;
185 }
186 case LOGIN:
187 {
188 // the server sends fixed responses (base64("Username") and
189 // base64("Password")), so we don't have to read them.
190 if (sendData(
191 new String(Base64.encodeBase64(username.getBytes()))) != IMAPReply.CONT)
192 {
193 return false;
194 }
195 int result = sendData(
196 new String(Base64.encodeBase64(password.getBytes())));
197 if (result == IMAPReply.OK)
198 {
199 setState(IMAP.IMAPState.AUTH_STATE);
200 }
201 return result == IMAPReply.OK;
202 }
203 }
204 return false; // safety check
205 }
206
207 /**
208 * Converts the given byte array to a String containing the hex values of the bytes.
209 * For example, the byte 'A' will be converted to '41', because this is the ASCII code
210 * (and the byte value) of the capital letter 'A'.
211 * @param a The byte array to convert.
212 * @return The resulting String of hex codes.
213 */
214 private String _convertToHexString(byte[] a)
215 {
216 StringBuilder result = new StringBuilder(a.length*2);
217 for (int i = 0; i < a.length; i++)
218 {
219 if ( (a[i] & 0x0FF) <= 15 ) {
220 result.append("0");
221 }
222 result.append(Integer.toHexString(a[i] & 0x0FF));
223 }
224 return result.toString();
225 }
226
227 /**
228 * The enumeration of currently-supported authentication methods.
229 */
230 public static enum AUTH_METHOD
231 {
232 /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */
233 PLAIN("PLAIN"),
234 /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */
235 CRAM_MD5("CRAM-MD5"),
236 /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */
237 LOGIN("LOGIN");
238
239 private final String authName;
240
241 private AUTH_METHOD(String name){
242 this.authName=name;
243 }
244 /**
245 * Gets the name of the given authentication method suitable for the server.
246 * @return The name of the given authentication method suitable for the server.
247 */
248 public final String getAuthName()
249 {
250 return authName;
251 }
252 }
253 }
254 /* kate: indent-width 4; replace-tabs on; */