Skip to contentPackage: SMTPSaslAuthenticator$1
SMTPSaslAuthenticator$1
Coverage
1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.smtp;
18:
19: import jakarta.mail.MessagingException;
20:
21: import java.util.Base64;
22: import java.util.Map;
23: import java.util.Properties;
24: import java.util.logging.Level;
25:
26: import javax.security.auth.callback.Callback;
27: import javax.security.auth.callback.CallbackHandler;
28: import javax.security.auth.callback.NameCallback;
29: import javax.security.auth.callback.PasswordCallback;
30: import javax.security.sasl.RealmCallback;
31: import javax.security.sasl.RealmChoiceCallback;
32: import javax.security.sasl.Sasl;
33: import javax.security.sasl.SaslClient;
34: import javax.security.sasl.SaslException;
35:
36: import org.eclipse.angus.mail.util.ASCIIUtility;
37: import org.eclipse.angus.mail.auth.OAuth2SaslClientFactory;
38: import org.eclipse.angus.mail.util.MailLogger;
39:
40: /**
41: * This class contains a single method that does authentication using
42: * SASL. This is in a separate class so that it can be compiled with
43: * J2SE 1.5. Eventually it should be merged into SMTPTransport.java.
44: */
45:
46: public class SMTPSaslAuthenticator implements SaslAuthenticator {
47:
48: private SMTPTransport pr;
49: private String name;
50: private Properties props;
51: private MailLogger logger;
52: private String host;
53:
54: /*
55: * This is a hack to initialize the OAUTH SASL provider just before,
56: * and only if, we might need it. This avoids the need for the user
57: * to initialize it explicitly, or manually configure the security
58: * providers file.
59: */
60: static {
61:         try {
62:          OAuth2SaslClientFactory.init();
63:         } catch (Throwable t) { }
64: }
65:
66: public SMTPSaslAuthenticator(SMTPTransport pr, String name,
67:                 Properties props, MailLogger logger, String host) {
68:         this.pr = pr;
69:         this.name = name;
70:         this.props = props;
71:         this.logger = logger;
72:         this.host = host;
73: }
74:
75: @Override
76: public boolean authenticate(String[] mechs, final String realm,
77:                                 final String authzid, final String u,
78:                                 final String p) throws MessagingException {
79:
80:         boolean done = false;
81:         if (logger.isLoggable(Level.FINE)) {
82:          logger.fine("SASL Mechanisms:");
83:          for (int i = 0; i < mechs.length; i++)
84:                 logger.fine(" " + mechs[i]);
85:          logger.fine("");
86:         }
87:
88:         SaslClient sc;
89:         CallbackHandler cbh = new CallbackHandler() {
90:          @Override
91:          public void handle(Callback[] callbacks) {
92:•                if (logger.isLoggable(Level.FINE))
93:                  logger.fine("SASL callback length: " + callbacks.length);
94:•                for (int i = 0; i < callbacks.length; i++) {
95:•                 if (logger.isLoggable(Level.FINE))
96:                         logger.fine("SASL callback " + i + ": " + callbacks[i]);
97:•                 if (callbacks[i] instanceof NameCallback) {
98:                         NameCallback ncb = (NameCallback)callbacks[i];
99:                         ncb.setName(u);
100:•                 } else if (callbacks[i] instanceof PasswordCallback) {
101:                         PasswordCallback pcb = (PasswordCallback)callbacks[i];
102:                         pcb.setPassword(p.toCharArray());
103:•                 } else if (callbacks[i] instanceof RealmCallback) {
104:                         RealmCallback rcb = (RealmCallback)callbacks[i];
105:•                        rcb.setText(realm != null ?
106:                                  realm : rcb.getDefaultText());
107:•                 } else if (callbacks[i] instanceof RealmChoiceCallback) {
108:                         RealmChoiceCallback rcb =
109:                          (RealmChoiceCallback)callbacks[i];
110:•                        if (realm == null)
111:                          rcb.setSelectedIndex(rcb.getDefaultChoice());
112:                         else {
113:                          // need to find specified realm in list
114:                          String[] choices = rcb.getChoices();
115:•                         for (int k = 0; k < choices.length; k++) {
116:•                                if (choices[k].equals(realm)) {
117:                                  rcb.setSelectedIndex(k);
118:                                  break;
119:                                 }
120:                          }
121:                         }
122:                  }
123:                 }
124:          }
125:         };
126:
127:         try {
128:          @SuppressWarnings("unchecked")
129:          Map<String, ?> propsMap = (Map) props;
130:          sc = Sasl.createSaslClient(mechs, authzid, name, host,
131:                                         propsMap, cbh);
132:         } catch (SaslException sex) {
133:          logger.log(Level.FINE, "Failed to create SASL client", sex);
134:          throw new UnsupportedOperationException(sex.getMessage(), sex);
135:         }
136:         if (sc == null) {
137:          logger.fine("No SASL support");
138:          throw new UnsupportedOperationException("No SASL support");
139:         }
140:         if (logger.isLoggable(Level.FINE))
141:          logger.fine("SASL client " + sc.getMechanismName());
142:
143:         int resp;
144:         try {
145:          String mech = sc.getMechanismName();
146:          String ir = null;
147:          if (sc.hasInitialResponse()) {
148:                 byte[] ba = sc.evaluateChallenge(new byte[0]);
149:                 if (ba.length > 0) {
150:                  ba = Base64.getEncoder().encode(ba);
151:                  ir = ASCIIUtility.toString(ba, 0, ba.length);
152:                 } else
153:                  ir = "=";
154:          }
155:          if (ir != null)
156:                 resp = pr.simpleCommand("AUTH " + mech + " " + ir);
157:          else
158:                 resp = pr.simpleCommand("AUTH " + mech);
159:
160:          /*
161:          * A 530 response indicates that the server wants us to
162:          * issue a STARTTLS command first. Do that and try again.
163:          */
164:          if (resp == 530) {
165:                 pr.startTLS();
166:                 if (ir != null)
167:                  resp = pr.simpleCommand("AUTH " + mech + " " + ir);
168:                 else
169:                  resp = pr.simpleCommand("AUTH " + mech);
170:          }
171:
172:          if (resp == 235)
173:                 return true;        // success already!
174:
175:          if (resp != 334)
176:                 return false;
177:         } catch (Exception ex) {
178:          logger.log(Level.FINE, "SASL AUTHENTICATE Exception", ex);
179:          return false;
180:         }
181:
182:         while (!done) { // loop till we are done
183:          try {
184:                  if (resp == 334) {
185:                  byte[] ba = null;
186:                  if (!sc.isComplete()) {
187:                         ba = ASCIIUtility.getBytes(responseText(pr));
188:                         if (ba.length > 0)
189:                          ba = Base64.getDecoder().decode(ba);
190:                         if (logger.isLoggable(Level.FINE))
191:                          logger.fine("SASL challenge: " +
192:                                 ASCIIUtility.toString(ba, 0, ba.length) + " :");
193:                         ba = sc.evaluateChallenge(ba);
194:                  }
195:                  if (ba == null) {
196:                         logger.fine("SASL: no response");
197:                         resp = pr.simpleCommand("");
198:                  } else {
199:                         if (logger.isLoggable(Level.FINE))
200:                          logger.fine("SASL response: " +
201:                                 ASCIIUtility.toString(ba, 0, ba.length) + " :");
202:                         ba = Base64.getEncoder().encode(ba);
203:                         resp = pr.simpleCommand(ba);
204:                  }
205:                 } else
206:                  done = true;
207:          } catch (Exception ioex) {
208:                 logger.log(Level.FINE, "SASL Exception", ioex);
209:                 done = true;
210:                 // XXX - ultimately return true???
211:          }
212:         }
213:         if (resp != 235)
214:          return false;
215:
216:         if (sc.isComplete() /*&& res.status == SUCCESS*/) {
217:          String qop = (String)sc.getNegotiatedProperty(Sasl.QOP);
218:          if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
219:                                 qop.equalsIgnoreCase("auth-conf"))) {
220:                 // XXX - NOT SUPPORTED!!!
221:                 logger.fine(
222:                  "SASL Mechanism requires integrity or confidentiality");
223:                 return false;
224:          }
225:         }
226:
227:         return true;
228: }
229:
230: private static final String responseText(SMTPTransport pr) {
231:         String resp = pr.getLastServerResponse().trim();
232:         if (resp.length() > 4)
233:          return resp.substring(4);
234:         else
235:          return "";
236: }
237: }