1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.geronimo.javamail.authentication;
21
22 import java.io.UnsupportedEncodingException ;
23 import java.util.Map;
24 import java.util.Properties;
25
26 import javax.mail.MessagingException;
27
28 import javax.security.auth.callback.Callback;
29 import javax.security.auth.callback.CallbackHandler;
30 import javax.security.auth.callback.NameCallback;
31 import javax.security.auth.callback.PasswordCallback;
32 import javax.security.sasl.Sasl;
33 import javax.security.sasl.SaslClient;
34 import javax.security.sasl.SaslException;
35 import javax.security.sasl.RealmCallback;
36 import javax.security.sasl.RealmChoiceCallback;
37
38 public class SASLAuthenticator implements ClientAuthenticator, CallbackHandler {
39 // The realm we're authenticating within
40 protected String realm;
41 // the user we're authenticating
42 protected String username;
43 // the user's password (the "shared secret")
44 protected String password;
45 // the authenticator we're proxying
46 protected SaslClient authenticator;
47
48 protected boolean complete = false;
49
50 /**
51 * Main constructor.
52 *
53 * @param username
54 * The login user name.
55 * @param password
56 * The login password.
57 */
58 public SASLAuthenticator(String[] mechanisms, Properties properties, String protocol, String host, String realm,
59 String authorizationID, String username, String password) throws MessagingException {
60 this.realm = realm;
61 this.username = username;
62 this.password = password;
63 try {
64 authenticator = Sasl.createSaslClient(mechanisms, authorizationID, protocol, host, (Map)properties,
65 this);
66 } catch (SaslException e) {
67 }
68 }
69
70
71 /**
72 * Respond to the hasInitialResponse query. We defer this to the Sasl client.
73 *
74 * @return The SaslClient response to the same query.
75 */
76 public boolean hasInitialResponse() {
77 return authenticator.hasInitialResponse();
78 }
79
80 /**
81 * Indicate whether the challenge/response process is complete.
82 *
83 * @return True if the last challenge has been processed, false otherwise.
84 */
85 public boolean isComplete() {
86 return authenticator.hasInitialResponse();
87 }
88
89 /**
90 * Retrieve the authenticator mechanism name.
91 *
92 * @return Always returns the string "PLAIN"
93 */
94 public String getMechanismName() {
95 // the authenticator selects this for us.
96 return authenticator.getMechanismName();
97 }
98
99 /**
100 * Evaluate a login challenge, returning the a result string that
101 * should satisfy the clallenge. This is forwarded to the
102 * SaslClient, which will use the CallBackHandler to retrieve the
103 * information it needs for the given protocol.
104 *
105 * @param challenge
106 * The decoded challenge data, as byte array.
107 *
108 * @return A formatted challege response, as an array of bytes.
109 * @exception MessagingException
110 */
111 public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
112 // for an initial response challenge, there's no challenge date. The SASL
113 // client still expects a byte array argument.
114 if (challenge == null) {
115 challenge = new byte[0];
116 }
117
118 try {
119 return authenticator.evaluateChallenge(challenge);
120 } catch (SaslException e) {
121 // got an error, fail this
122 throw new MessagingException("Error performing SASL validation", e);
123 }
124 }
125
126 public void handle(Callback[] callBacks) {
127 for (int i = 0; i < callBacks.length; i++) {
128 Callback callBack = callBacks[i];
129 // requesting the user name
130 if (callBack instanceof NameCallback) {
131 ((NameCallback)callBack).setName(username);
132 }
133 // need the password
134 else if (callBack instanceof PasswordCallback) {
135 ((PasswordCallback)callBack).setPassword(password.toCharArray());
136 }
137 // direct request for the realm information
138 else if (callBack instanceof RealmCallback) {
139 RealmCallback realmCallback = (RealmCallback)callBack;
140 // we might not have a realm, so use the default from the
141 // callback item
142 if (realm == null) {
143 realmCallback.setText(realmCallback.getDefaultText());
144 }
145 else {
146 realmCallback.setText(realm);
147 }
148 }
149 // asked to select the realm information from a list
150 else if (callBack instanceof RealmChoiceCallback) {
151 RealmChoiceCallback realmCallback = (RealmChoiceCallback)callBack;
152 // if we don't have a realm, just tell it to use the default
153 if (realm == null) {
154 realmCallback.setSelectedIndex(realmCallback.getDefaultChoice());
155 }
156 else {
157 // locate our configured one in the list
158 String[] choices = realmCallback.getChoices();
159
160 for (int j = 0; j < choices.length; j++) {
161 // set the index to any match and get out of here.
162 if (choices[j].equals(realm)) {
163 realmCallback.setSelectedIndex(j);
164 break;
165 }
166 }
167 // NB: If there was no match, we don't set anything.
168 // this should cause an authentication failure.
169 }
170 }
171 }
172 }
173 }
174