/
AuthenticateProcessor.java
200 lines (176 loc) · 8.96 KB
/
AuthenticateProcessor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.imap.processor;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.codec.binary.Base64;
import org.apache.james.imap.api.ImapCommand;
import org.apache.james.imap.api.display.HumanReadableText;
import org.apache.james.imap.api.message.response.StatusResponseFactory;
import org.apache.james.imap.api.process.ImapLineHandler;
import org.apache.james.imap.api.process.ImapProcessor;
import org.apache.james.imap.api.process.ImapSession;
import org.apache.james.imap.message.request.AuthenticateRequest;
import org.apache.james.imap.message.request.IRAuthenticateRequest;
import org.apache.james.imap.message.response.AuthenticateResponse;
import org.apache.james.mailbox.MailboxManager;
import com.google.common.base.Optional;
/**
* Processor which handles the AUTHENTICATE command. Only authtype of PLAIN is supported ATM.
*
*
*/
public class AuthenticateProcessor extends AbstractAuthProcessor<AuthenticateRequest> implements CapabilityImplementingProcessor{
private final static String PLAIN = "PLAIN";
public AuthenticateProcessor(ImapProcessor next, MailboxManager mailboxManager, StatusResponseFactory factory) {
super(AuthenticateRequest.class, next, mailboxManager, factory);
}
/**
* @see
* org.apache.james.imap.processor.AbstractMailboxProcessor#doProcess(org.apache.james.imap.api.message.request.ImapRequest,
* org.apache.james.imap.api.process.ImapSession, java.lang.String,
* org.apache.james.imap.api.ImapCommand,
* org.apache.james.imap.api.process.ImapProcessor.Responder)
*/
protected void doProcess(AuthenticateRequest request, ImapSession session, final String tag, final ImapCommand command, final Responder responder) {
final String authType = request.getAuthType();
if (authType.equalsIgnoreCase(PLAIN)) {
// See if AUTH=PLAIN is allowed. See IMAP-304
if (session.isPlainAuthDisallowed() && session.isTLSActive() == false) {
no(command, tag, responder, HumanReadableText.DISABLED_LOGIN);
} else {
if (request instanceof IRAuthenticateRequest) {
IRAuthenticateRequest irRequest = (IRAuthenticateRequest) request;
doPlainAuth(irRequest.getInitialClientResponse(), session, tag, command, responder);
} else {
responder.respond(new AuthenticateResponse());
session.pushLineHandler(new ImapLineHandler() {
public void onLine(ImapSession session, byte[] data) {
// cut of the CRLF
String initialClientResponse = new String(data, 0, data.length - 2, Charset.forName("US-ASCII"));
doPlainAuth(initialClientResponse, session, tag, command, responder);
// remove the handler now
session.popLineHandler();
}
});
}
}
} else {
if (session.getLog().isDebugEnabled()) {
session.getLog().debug ("Unsupported authentication mechanism '" + authType + "'");
}
no(command, tag, responder, HumanReadableText.UNSUPPORTED_AUTHENTICATION_MECHANISM);
}
}
/**
* Parse the initialClientResponse and do a PLAIN AUTH with it
*
* @param initialClientResponse
* @param session
* @param tag
* @param command
* @param responder
*/
protected void doPlainAuth(String initialClientResponse, ImapSession session, String tag, ImapCommand command, Responder responder) {
AuthPlainAttempt authPlainAttempt = parseDelegationAttempt(initialClientResponse);
// Authenticate user
doAuth(authPlainAttempt.getAuthenticationId(), authPlainAttempt.getPassword(), session, tag, command, responder, HumanReadableText.AUTHENTICATION_FAILED);
}
private AuthPlainAttempt parseDelegationAttempt(String initialClientResponse) {
String token2;
try {
String userpass = new String(Base64.decodeBase64(initialClientResponse));
StringTokenizer authTokenizer = new StringTokenizer(userpass, "\0");
String token1 = authTokenizer.nextToken(); // Authorization Identity
token2 = authTokenizer.nextToken(); // Authentication Identity
try {
return delegation(token1, token2, authTokenizer.nextToken());
} catch (java.util.NoSuchElementException _) {
// If we got here, this is what happened. RFC 2595
// says that "the client may leave the authorization
// identity empty to indicate that it is the same as
// the authentication identity." As noted above,
// that would be represented as a decoded string of
// the form: "\0authenticate-id\0password". The
// first call to nextToken will skip the empty
// authorize-id, and give us the authenticate-id,
// which we would store as the authorize-id. The
// second call will give us the password, which we
// think is the authenticate-id (user). Then when
// we ask for the password, there are no more
// elements, leading to the exception we just
// caught. So we need to move the user to the
// password, and the authorize_id to the user.
return noDelegation(token1, token2);
} finally {
authTokenizer = null;
}
} catch (Exception e) {
// Ignored - this exception in parsing will be dealt
// with in the if clause below
return noDelegation(null, null);
}
}
/**
* @see org.apache.james.imap.processor.CapabilityImplementingProcessor
* #getImplementedCapabilities(org.apache.james.imap.api.process.ImapSession)
*/
public List<String> getImplementedCapabilities(ImapSession session) {
List<String> caps = new ArrayList<String>();
// Only ounce AUTH=PLAIN if the session does allow plain auth or TLS is active.
// See IMAP-304
if (session.isPlainAuthDisallowed() == false || session.isTLSActive()) {
caps.add("AUTH=PLAIN");
}
// Support for SASL-IR. See RFC4959
caps.add("SASL-IR");
return Collections.unmodifiableList(caps);
}
private static AuthPlainAttempt delegation(String authorizeId, String authenticationId, String password) {
return new AuthPlainAttempt(Optional.of(authorizeId), authenticationId, password);
}
private static AuthPlainAttempt noDelegation(String authenticationId, String password) {
return new AuthPlainAttempt(Optional.<String>absent(), authenticationId, password);
}
private static class AuthPlainAttempt {
private final Optional<String> authorizeId;
private final String authenticationId;
private final String password;
private AuthPlainAttempt(Optional<String> authorizeId, String authenticationId, String password) {
this.authorizeId = authorizeId;
this.authenticationId = authenticationId;
this.password = password;
}
public boolean isDelegation() {
return authorizeId.isPresent();
}
public Optional<String> getAuthorizeId() {
return authorizeId;
}
public String getAuthenticationId() {
return authenticationId;
}
public String getPassword() {
return password;
}
}
}