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; */