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