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