001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.geronimo.javamail.authentication;
021
022 import java.io.UnsupportedEncodingException ;
023 import java.util.Map;
024 import java.util.Properties;
025
026 import javax.mail.MessagingException;
027
028 import javax.security.auth.callback.Callback;
029 import javax.security.auth.callback.CallbackHandler;
030 import javax.security.auth.callback.NameCallback;
031 import javax.security.auth.callback.PasswordCallback;
032 import javax.security.sasl.Sasl;
033 import javax.security.sasl.SaslClient;
034 import javax.security.sasl.SaslException;
035 import javax.security.sasl.RealmCallback;
036 import javax.security.sasl.RealmChoiceCallback;
037
038 public class SASLAuthenticator implements ClientAuthenticator, CallbackHandler {
039 // The realm we're authenticating within
040 protected String realm;
041 // the user we're authenticating
042 protected String username;
043 // the user's password (the "shared secret")
044 protected String password;
045 // the authenticator we're proxying
046 protected SaslClient authenticator;
047
048 protected boolean complete = false;
049
050 /**
051 * Main constructor.
052 *
053 * @param username
054 * The login user name.
055 * @param password
056 * The login password.
057 */
058 public SASLAuthenticator(String[] mechanisms, Properties properties, String protocol, String host, String realm,
059 String authorizationID, String username, String password) throws MessagingException {
060 this.realm = realm;
061 this.username = username;
062 this.password = password;
063 try {
064 authenticator = Sasl.createSaslClient(mechanisms, authorizationID, protocol, host, (Map)properties,
065 this);
066 } catch (SaslException e) {
067 }
068 }
069
070
071 /**
072 * Respond to the hasInitialResponse query. We defer this to the Sasl client.
073 *
074 * @return The SaslClient response to the same query.
075 */
076 public boolean hasInitialResponse() {
077 return authenticator.hasInitialResponse();
078 }
079
080 /**
081 * Indicate whether the challenge/response process is complete.
082 *
083 * @return True if the last challenge has been processed, false otherwise.
084 */
085 public boolean isComplete() {
086 return authenticator.hasInitialResponse();
087 }
088
089 /**
090 * Retrieve the authenticator mechanism name.
091 *
092 * @return Always returns the string "PLAIN"
093 */
094 public String getMechanismName() {
095 // the authenticator selects this for us.
096 return authenticator.getMechanismName();
097 }
098
099 /**
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