1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.net.imap;
19
20 import java.io.IOException;
21 import java.security.InvalidKeyException;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.Base64;
24
25 import javax.crypto.Mac;
26 import javax.crypto.spec.SecretKeySpec;
27 import javax.net.ssl.SSLContext;
28
29
30
31
32
33
34 public class AuthenticatingIMAPClient extends IMAPSClient {
35
36
37
38
39 public enum AUTH_METHOD {
40
41
42
43 PLAIN("PLAIN"),
44
45
46 CRAM_MD5("CRAM-MD5"),
47
48
49 LOGIN("LOGIN"),
50
51
52 XOAUTH("XOAUTH"),
53
54
55 XOAUTH2("XOAUTH2");
56
57 private final String authName;
58
59 AUTH_METHOD(final String name) {
60 this.authName = name;
61 }
62
63
64
65
66
67
68 public String getAuthName() {
69 return authName;
70 }
71 }
72
73
74 private static final String MAC_ALGORITHM = "HmacMD5";
75
76
77
78
79 public AuthenticatingIMAPClient() {
80 this(DEFAULT_PROTOCOL, false);
81 }
82
83
84
85
86
87
88 public AuthenticatingIMAPClient(final boolean implicit) {
89 this(DEFAULT_PROTOCOL, implicit);
90 }
91
92
93
94
95
96
97
98 public AuthenticatingIMAPClient(final boolean implicit, final SSLContext ctx) {
99 this(DEFAULT_PROTOCOL, implicit, ctx);
100 }
101
102
103
104
105
106
107 public AuthenticatingIMAPClient(final SSLContext context) {
108 this(false, context);
109 }
110
111
112
113
114
115
116 public AuthenticatingIMAPClient(final String proto) {
117 this(proto, false);
118 }
119
120
121
122
123
124
125
126 public AuthenticatingIMAPClient(final String proto, final boolean implicit) {
127 this(proto, implicit, null);
128 }
129
130
131
132
133
134
135
136
137 public AuthenticatingIMAPClient(final String proto, final boolean implicit, final SSLContext ctx) {
138 super(proto, implicit, ctx);
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152 public boolean auth(final AuthenticatingIMAPClient.AUTH_METHOD method, final String user, final String password)
153 throws IOException, NoSuchAlgorithmException, InvalidKeyException {
154 if (!IMAPReply.isContinuation(sendCommand(IMAPCommand.AUTHENTICATE, method.getAuthName()))) {
155 return false;
156 }
157
158 switch (method) {
159 case PLAIN: {
160
161 final int result = sendData(Base64.getEncoder().encodeToString(("\000" + user + "\000" + password).getBytes(getCharset())));
162 if (result == IMAPReply.OK) {
163 setState(IMAP.IMAPState.AUTH_STATE);
164 }
165 return result == IMAPReply.OK;
166 }
167 case CRAM_MD5: {
168
169 final byte[] serverChallenge = Base64.getDecoder().decode(getReplyString().substring(2).trim());
170
171 final Mac hmacMd5 = Mac.getInstance(MAC_ALGORITHM);
172 hmacMd5.init(new SecretKeySpec(password.getBytes(getCharset()), MAC_ALGORITHM));
173
174 final byte[] hmacResult = convertToHexString(hmacMd5.doFinal(serverChallenge)).getBytes(getCharset());
175
176 final byte[] usernameBytes = user.getBytes(getCharset());
177 final byte[] toEncode = new byte[usernameBytes.length + 1 + hmacResult.length];
178 System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length);
179 toEncode[usernameBytes.length] = ' ';
180 System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length);
181
182 final int result = sendData(Base64.getEncoder().encodeToString(toEncode));
183 if (result == IMAPReply.OK) {
184 setState(IMAP.IMAPState.AUTH_STATE);
185 }
186 return result == IMAPReply.OK;
187 }
188 case LOGIN: {
189
190
191 if (sendData(Base64.getEncoder().encodeToString(user.getBytes(getCharset()))) != IMAPReply.CONT) {
192 return false;
193 }
194 final int result = sendData(Base64.getEncoder().encodeToString(password.getBytes(getCharset())));
195 if (result == IMAPReply.OK) {
196 setState(IMAP.IMAPState.AUTH_STATE);
197 }
198 return result == IMAPReply.OK;
199 }
200 case XOAUTH:
201 case XOAUTH2: {
202 final int result = sendData(user);
203 if (result == IMAPReply.OK) {
204 setState(IMAP.IMAPState.AUTH_STATE);
205 }
206 return result == IMAPReply.OK;
207 }
208 }
209 return false;
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223 public boolean authenticate(final AuthenticatingIMAPClient.AUTH_METHOD method, final String user, final String password)
224 throws IOException, NoSuchAlgorithmException, InvalidKeyException {
225 return auth(method, user, password);
226 }
227
228
229
230
231
232
233
234
235 private String convertToHexString(final byte[] a) {
236 final StringBuilder result = new StringBuilder(a.length * 2);
237 for (final byte element : a) {
238 if ((element & 0x0FF) <= 15) {
239 result.append("0");
240 }
241 result.append(Integer.toHexString(element & 0x0FF));
242 }
243 return result.toString();
244 }
245 }
246