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