Package: SMTPTransport
SMTPTransport
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SMTPTransport(Session, URLName) |
|
|
|
|
|
||||||||||||||||||||
SMTPTransport(Session, URLName, String, boolean) |
|
|
|
|
|
||||||||||||||||||||
addressesFailed() |
|
|
|
|
|
||||||||||||||||||||
authenticate(String, String) |
|
|
|
|
|
||||||||||||||||||||
bdat() |
|
|
|
|
|
||||||||||||||||||||
checkConnected() |
|
|
|
|
|
||||||||||||||||||||
close() |
|
|
|
|
|
||||||||||||||||||||
closeConnection() |
|
|
|
|
|
||||||||||||||||||||
connect(Socket) |
|
|
|
|
|
||||||||||||||||||||
convertTo8Bit(MimePart) |
|
|
|
|
|
||||||||||||||||||||
data() |
|
|
|
|
|
||||||||||||||||||||
ehlo(String) |
|
|
|
|
|
||||||||||||||||||||
expandGroups() |
|
|
|
|
|
||||||||||||||||||||
finalize() |
|
|
|
|
|
||||||||||||||||||||
finishBdat() |
|
|
|
|
|
||||||||||||||||||||
finishData() |
|
|
|
|
|
||||||||||||||||||||
getAuthorizationId() |
|
|
|
|
|
||||||||||||||||||||
getExtensionParameter(String) |
|
|
|
|
|
||||||||||||||||||||
getLastReturnCode() |
|
|
|
|
|
||||||||||||||||||||
getLastServerResponse() |
|
|
|
|
|
||||||||||||||||||||
getLocalHost() |
|
|
|
|
|
||||||||||||||||||||
getNTLMDomain() |
|
|
|
|
|
||||||||||||||||||||
getNoopStrict() |
|
|
|
|
|
||||||||||||||||||||
getReportSuccess() |
|
|
|
|
|
||||||||||||||||||||
getRequireStartTLS() |
|
|
|
|
|
||||||||||||||||||||
getSASLEnabled() |
|
|
|
|
|
||||||||||||||||||||
getSASLMechanisms() |
|
|
|
|
|
||||||||||||||||||||
getSASLRealm() |
|
|
|
|
|
||||||||||||||||||||
getStartTLS() |
|
|
|
|
|
||||||||||||||||||||
getUseCanonicalHostName() |
|
|
|
|
|
||||||||||||||||||||
getUseRset() |
|
|
|
|
|
||||||||||||||||||||
helo(String) |
|
|
|
|
|
||||||||||||||||||||
initStreams() |
|
|
|
|
|
||||||||||||||||||||
is8Bit(InputStream) |
|
|
|
|
|
||||||||||||||||||||
isConnected() |
|
|
|
|
|
||||||||||||||||||||
isNotLastLine(String) |
|
|
|
|
|
||||||||||||||||||||
isSSL() |
|
|
|
|
|
||||||||||||||||||||
isTracing() |
|
|
|
|
|
||||||||||||||||||||
issueCommand(String, int) |
|
|
|
|
|
||||||||||||||||||||
issueSendCommand(String, int) |
|
|
|
|
|
||||||||||||||||||||
mailFrom() |
|
|
|
|
|
||||||||||||||||||||
normalizeAddress(String) |
|
|
|
|
|
||||||||||||||||||||
notifyTransportListeners(int, Address[], Address[], Address[], Message) |
|
|
|
|
|
||||||||||||||||||||
openServer() |
|
|
|
|
|
||||||||||||||||||||
openServer(String, int) |
|
|
|
|
|
||||||||||||||||||||
protocolConnect(String, int, String, String) |
|
|
|
|
|
||||||||||||||||||||
rcptTo() |
|
|
|
|
|
||||||||||||||||||||
readServerResponse() |
|
|
|
|
|
||||||||||||||||||||
resumeTracing() |
|
|
|
|
|
||||||||||||||||||||
sasllogin(String[], String, String, String, String) |
|
|
|
|
|
||||||||||||||||||||
sendCommand(String) |
|
|
|
|
|
||||||||||||||||||||
sendCommand(byte[]) |
|
|
|
|
|
||||||||||||||||||||
sendMessage(Message, Address[]) |
|
|
|
|
|
||||||||||||||||||||
sendMessageEnd() |
|
|
|
|
|
||||||||||||||||||||
sendMessageStart(String) |
|
|
|
|
|
||||||||||||||||||||
setAuthorizationID(String) |
|
|
|
|
|
||||||||||||||||||||
setLocalHost(String) |
|
|
|
|
|
||||||||||||||||||||
setNTLMDomain(String) |
|
|
|
|
|
||||||||||||||||||||
setNoopStrict(boolean) |
|
|
|
|
|
||||||||||||||||||||
setReportSuccess(boolean) |
|
|
|
|
|
||||||||||||||||||||
setRequireStartTLS(boolean) |
|
|
|
|
|
||||||||||||||||||||
setSASLEnabled(boolean) |
|
|
|
|
|
||||||||||||||||||||
setSASLMechanisms(String[]) |
|
|
|
|
|
||||||||||||||||||||
setSASLRealm(String) |
|
|
|
|
|
||||||||||||||||||||
setStartTLS(boolean) |
|
|
|
|
|
||||||||||||||||||||
setUseCanonicalHostName(boolean) |
|
|
|
|
|
||||||||||||||||||||
setUseRset(boolean) |
|
|
|
|
|
||||||||||||||||||||
simpleCommand(String) |
|
|
|
|
|
||||||||||||||||||||
simpleCommand(byte[]) |
|
|
|
|
|
||||||||||||||||||||
startTLS() |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
supportsAuthentication(String) |
|
|
|
|
|
||||||||||||||||||||
supportsExtension(String) |
|
|
|
|
|
||||||||||||||||||||
suspendTracing() |
|
|
|
|
|
||||||||||||||||||||
toBytes(String) |
|
|
|
|
|
||||||||||||||||||||
tracePassword(String) |
|
|
|
|
|
||||||||||||||||||||
traceUser(String) |
|
|
|
|
|
||||||||||||||||||||
xtext(String) |
|
|
|
|
|
||||||||||||||||||||
xtext(String, boolean) |
|
|
|
|
|
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 java.io.BufferedInputStream;
20: import java.io.BufferedOutputStream;
21: import java.io.BufferedReader;
22: import java.io.ByteArrayOutputStream;
23: import java.io.IOException;
24: import java.io.InputStream;
25: import java.io.OutputStream;
26: import java.io.StringReader;
27: import java.lang.reflect.Constructor;
28: import java.net.InetAddress;
29: import java.net.Socket;
30: import java.net.UnknownHostException;
31: import java.nio.charset.StandardCharsets;
32: import java.util.ArrayList;
33: import java.util.Base64;
34: import java.util.HashMap;
35: import java.util.Hashtable;
36: import java.util.List;
37: import java.util.Locale;
38: import java.util.Map;
39: import java.util.Properties;
40: import java.util.StringTokenizer;
41: import java.util.logging.Level;
42:
43: import javax.net.ssl.SSLSocket;
44:
45: import org.eclipse.angus.mail.auth.Ntlm;
46: import org.eclipse.angus.mail.util.BASE64EncoderStream;
47: import org.eclipse.angus.mail.util.LineInputStream;
48: import org.eclipse.angus.mail.util.ASCIIUtility;
49: import org.eclipse.angus.mail.util.MailConnectException;
50: import org.eclipse.angus.mail.util.MailLogger;
51: import org.eclipse.angus.mail.util.PropUtil;
52: import org.eclipse.angus.mail.util.SocketConnectException;
53: import org.eclipse.angus.mail.util.SocketFetcher;
54: import org.eclipse.angus.mail.util.TraceInputStream;
55: import org.eclipse.angus.mail.util.TraceOutputStream;
56:
57: import jakarta.mail.Address;
58: import jakarta.mail.AuthenticationFailedException;
59: import jakarta.mail.Message;
60: import jakarta.mail.MessagingException;
61: import jakarta.mail.SendFailedException;
62: import jakarta.mail.Session;
63: import jakarta.mail.Transport;
64: import jakarta.mail.URLName;
65: import jakarta.mail.event.TransportEvent;
66: import jakarta.mail.internet.AddressException;
67: import jakarta.mail.internet.InternetAddress;
68: import jakarta.mail.internet.MimeMessage;
69: import jakarta.mail.internet.MimeMultipart;
70: import jakarta.mail.internet.MimePart;
71: import jakarta.mail.internet.ParseException;
72: import jakarta.mail.util.StreamProvider.EncoderTypes;
73:
74: /**
75: * This class implements the Transport abstract class using SMTP for
76: * message submission and transport. <p>
77: *
78: * See the <a href="package-summary.html">org.eclipse.angus.mail.smtp</a> package
79: * documentation for further information on the SMTP protocol provider. <p>
80: *
81: * This class includes many protected methods that allow a subclass to
82: * extend this class and add support for non-standard SMTP commands.
83: * The {@link #issueCommand} and {@link #sendCommand} methods can be
84: * used to send simple SMTP commands. Other methods such as the
85: * {@link #mailFrom} and {@link #data} methods can be overridden to
86: * insert new commands before or after the corresponding SMTP commands.
87: * For example, a subclass could do this to send the XACT command
88: * before sending the DATA command:
89: * <pre>
90: *        protected OutputStream data() throws MessagingException {
91: *         if (supportsExtension("XACCOUNTING"))
92: *         issueCommand("XACT", 25);
93: *         return super.data();
94: *        }
95: * </pre>
96: *
97: * @author Max Spivak
98: * @author Bill Shannon
99: * @author Dean Gibson (DIGEST-MD5 authentication)
100: * @author Lu\u00EDs Serralheiro (NTLM authentication)
101: *
102: * @see jakarta.mail.event.ConnectionEvent
103: * @see jakarta.mail.event.TransportEvent
104: */
105:
106: public class SMTPTransport extends Transport {
107:
108: private String name = "smtp";        // Name of this protocol
109: private int defaultPort = 25;        // default SMTP port
110: private boolean isSSL = false;        // use SSL?
111: private String host;                // host we're connected to
112:
113: // Following fields valid only during the sendMessage method.
114: private MimeMessage message;        // Message to be sent
115: private Address[] addresses;        // Addresses to which to send the msg
116: // Valid sent, valid unsent and invalid addresses
117: private Address[] validSentAddr, validUnsentAddr, invalidAddr;
118: // Did we send the message even though some addresses were invalid?
119: private boolean sendPartiallyFailed = false;
120: // If so, here's an exception we need to throw
121: private MessagingException exception;
122: // stream where message data is written
123: private SMTPOutputStream dataStream;
124:
125: // Map of SMTP service extensions supported by server, if EHLO used.
126: private Hashtable<String, String> extMap;
127:
128: private Map<String, Authenticator> authenticators
129:          = new HashMap<>();
130: private String defaultAuthenticationMechanisms;        // set in constructor
131:
132: private boolean quitWait = false;        // true if we should wait
133: private boolean quitOnSessionReject = false; // true if we should send quit when session initiation is rejected
134:
135: private String saslRealm = UNKNOWN;
136: private String authorizationID = UNKNOWN;
137: private boolean enableSASL = false;        // enable SASL authentication
138: private boolean useCanonicalHostName = false; // use canonical host name?
139: private String[] saslMechanisms = UNKNOWN_SA;
140:
141: private String ntlmDomain = UNKNOWN; // for ntlm authentication
142:
143: private boolean reportSuccess;        // throw an exception even on success
144: private boolean useStartTLS;        // use STARTTLS command
145: private boolean requireStartTLS;        // require STARTTLS command
146: private boolean useRset;                // use RSET instead of NOOP
147: private boolean noopStrict = true;        // NOOP must return 250 for success
148:
149: private MailLogger logger;                // debug logger
150: private MailLogger traceLogger;        // protocol trace logger
151: private String localHostName;        // our own host name
152: private String lastServerResponse;        // last SMTP response
153: private int lastReturnCode;                // last SMTP return code
154: private boolean notificationDone;        // only notify once per send
155:
156: private SaslAuthenticator saslAuthenticator; // if SASL is being used
157:
158: private boolean noauthdebug = true;        // hide auth info in debug output
159: private boolean debugusername;        // include username in debug output?
160: private boolean debugpassword;        // include password in debug output?
161: private boolean allowutf8;                // allow UTF-8 usernames and passwords?
162: private int chunkSize;                // chunk size if CHUNKING supported
163:
164: /** Headers that should not be included when sending */
165: private static final String[] ignoreList = { "Bcc", "Content-Length" };
166: private static final byte[] CRLF = { (byte)'\r', (byte)'\n' };
167: private static final String UNKNOWN = "UNKNOWN";        // place holder
168: private static final String[] UNKNOWN_SA = new String[0]; // place holder
169:
170: /**
171: * Constructor that takes a Session object and a URLName
172: * that represents a specific SMTP server.
173: *
174: * @param        session        the Session
175: * @param        urlname        the URLName of this transport
176: */
177: public SMTPTransport(Session session, URLName urlname) {
178:         this(session, urlname, "smtp", false);
179: }
180:
181: /**
182: * Constructor used by this class and by SMTPSSLTransport subclass.
183: *
184: * @param        session        the Session
185: * @param        urlname        the URLName of this transport
186: * @param        name        the protocol name of this transport
187: * @param        isSSL        use SSL to connect?
188: */
189: protected SMTPTransport(Session session, URLName urlname,
190:                                 String name, boolean isSSL) {
191:         super(session, urlname);
192:         Properties props = session.getProperties();
193:
194:         logger = new MailLogger(this.getClass(), "DEBUG SMTP",
195:                                 session.getDebug(), session.getDebugOut());
196:         traceLogger = logger.getSubLogger("protocol", null);
197:•        noauthdebug = !PropUtil.getBooleanProperty(props,
198:                          "mail.debug.auth", false);
199:         debugusername = PropUtil.getBooleanProperty(props,
200:                         "mail.debug.auth.username", true);
201:         debugpassword = PropUtil.getBooleanProperty(props,
202:                         "mail.debug.auth.password", false);
203:•        if (urlname != null)
204:          name = urlname.getProtocol();
205:         this.name = name;
206:•        if (!isSSL)
207:          isSSL = PropUtil.getBooleanProperty(props,
208:                                 "mail." + name + ".ssl.enable", false);
209:•        if (isSSL)
210:          this.defaultPort = 465;
211:         else
212:          this.defaultPort = 25;
213:         this.isSSL = isSSL;
214:
215:         // setting mail.smtp.quitwait to false causes us to not wait for the
216:         // response from the QUIT command
217:         quitWait = PropUtil.getBooleanProperty(props,
218:                                 "mail." + name + ".quitwait", true);
219:
220:         // setting mail.smtp.quitonsessionreject to false causes us to directly
221:         // close the socket without sending a QUIT command
222:         quitOnSessionReject = PropUtil.getBooleanProperty(props,
223: "mail." + name + ".quitonsessionreject", false);
224:
225:         // mail.smtp.reportsuccess causes us to throw an exception on success
226:         reportSuccess = PropUtil.getBooleanProperty(props,
227:                                 "mail." + name + ".reportsuccess", false);
228:
229:         // mail.smtp.starttls.enable enables use of STARTTLS command
230:         useStartTLS = PropUtil.getBooleanProperty(props,
231:                                 "mail." + name + ".starttls.enable", false);
232:
233:         // mail.smtp.starttls.required requires use of STARTTLS command
234:         requireStartTLS = PropUtil.getBooleanProperty(props,
235:                                 "mail." + name + ".starttls.required", false);
236:
237:         // mail.smtp.userset causes us to use RSET instead of NOOP
238:         // for isConnected
239:         useRset = PropUtil.getBooleanProperty(props,
240:                                 "mail." + name + ".userset", false);
241:
242:         // mail.smtp.noop.strict requires 250 response to indicate success
243:         noopStrict = PropUtil.getBooleanProperty(props,
244:                                 "mail." + name + ".noop.strict", true);
245:
246:         // check if SASL is enabled
247:         enableSASL = PropUtil.getBooleanProperty(props,
248:          "mail." + name + ".sasl.enable", false);
249:•        if (enableSASL)
250:          logger.config("enable SASL");
251:         useCanonicalHostName = PropUtil.getBooleanProperty(props,
252:          "mail." + name + ".sasl.usecanonicalhostname", false);
253:•        if (useCanonicalHostName)
254:          logger.config("use canonical host name");
255:
256:         allowutf8 = PropUtil.getBooleanProperty(props,
257:          "mail.mime.allowutf8", false);
258:•        if (allowutf8)
259:          logger.config("allow UTF-8");
260:
261:         chunkSize = PropUtil.getIntProperty(props,
262:          "mail." + name + ".chunksize", -1);
263:•        if (chunkSize > 0 && logger.isLoggable(Level.CONFIG))
264:          logger.config("chunk size " + chunkSize);
265:
266:         // created here, because they're inner classes that reference "this"
267:         Authenticator[] a = new Authenticator[] {
268:          new LoginAuthenticator(),
269:          new PlainAuthenticator(),
270:          new DigestMD5Authenticator(),
271:          new NtlmAuthenticator(),
272:          new OAuth2Authenticator()
273:         };
274:         StringBuilder sb = new StringBuilder();
275:•        for (int i = 0; i < a.length; i++) {
276:          authenticators.put(a[i].getMechanism(), a[i]);
277:          sb.append(a[i].getMechanism()).append(' ');
278:         }
279:         defaultAuthenticationMechanisms = sb.toString();
280: }
281:
282: /**
283: * Get the name of the local host, for use in the EHLO and HELO commands.
284: * The property mail.smtp.localhost overrides mail.smtp.localaddress,
285: * which overrides what InetAddress would tell us.
286: *
287: * @return        the local host name
288: */
289: public synchronized String getLocalHost() {
290:         // get our hostname and cache it for future use
291:•        if (localHostName == null || localHostName.length() <= 0)
292:          localHostName =
293:                  session.getProperty("mail." + name + ".localhost");
294:•        if (localHostName == null || localHostName.length() <= 0)
295:          localHostName =
296:                  session.getProperty("mail." + name + ".localaddress");
297:         try {
298:•         if (localHostName == null || localHostName.length() <= 0) {
299:                 InetAddress localHost = InetAddress.getLocalHost();
300:                 localHostName = localHost.getCanonicalHostName();
301:                 // if we can't get our name, use local address literal
302:•                if (localHostName == null)
303:                  // XXX - not correct for IPv6
304:                  localHostName = "[" + localHost.getHostAddress() + "]";
305:          }
306:         } catch (UnknownHostException uhex) {
307:         }
308:
309:         // last chance, try to get our address from our socket
310:•        if (localHostName == null || localHostName.length() <= 0) {
311:•         if (serverSocket != null && serverSocket.isBound()) {
312:                 InetAddress localHost = serverSocket.getLocalAddress();
313:                 localHostName = localHost.getCanonicalHostName();
314:                 // if we can't get our name, use local address literal
315:•                if (localHostName == null)
316:                  // XXX - not correct for IPv6
317:                  localHostName = "[" + localHost.getHostAddress() + "]";
318:          }
319:         }
320:         return localHostName;
321: }
322:
323: /**
324: * Set the name of the local host, for use in the EHLO and HELO commands.
325: *
326: * @param        localhost        the local host name
327: * @since JavaMail 1.3.1
328: */
329: public synchronized void setLocalHost(String localhost) {
330:         localHostName = localhost;
331: }
332:
333: /**
334: * Start the SMTP protocol on the given socket, which was already
335: * connected by the caller. Useful for implementing the SMTP ATRN
336: * command (RFC 2645) where an existing connection is used when
337: * the server reverses roles and becomes the client.
338: *
339: * @param        socket        the already connected socket
340: * @exception        MessagingException for failures
341: * @since JavaMail 1.3.3
342: */
343: public synchronized void connect(Socket socket) throws MessagingException {
344:         serverSocket = socket;
345:         super.connect();
346: }
347:
348: /**
349: * Gets the authorization ID to be used for authentication.
350: *
351: * @return        the authorization ID to use for authentication.
352: *
353: * @since JavaMail 1.4.4
354: */
355: public synchronized String getAuthorizationId() {
356:•        if (authorizationID == UNKNOWN) {
357:          authorizationID =
358:                 session.getProperty("mail." + name + ".sasl.authorizationid");
359:         }
360:         return authorizationID;
361: }
362:
363: /**
364: * Sets the authorization ID to be used for authentication.
365: *
366: * @param        authzid                the authorization ID to use for
367: *                                authentication.
368: *
369: * @since JavaMail 1.4.4
370: */
371: public synchronized void setAuthorizationID(String authzid) {
372:         this.authorizationID = authzid;
373: }
374:
375: /**
376: * Is SASL authentication enabled?
377: *
378: * @return        true if SASL authentication is enabled
379: *
380: * @since JavaMail 1.4.4
381: */
382: public synchronized boolean getSASLEnabled() {
383:         return enableSASL;
384: }
385:
386: /**
387: * Set whether SASL authentication is enabled.
388: *
389: * @param        enableSASL        should we enable SASL authentication?
390: *
391: * @since JavaMail 1.4.4
392: */
393: public synchronized void setSASLEnabled(boolean enableSASL) {
394:         this.enableSASL = enableSASL;
395: }
396:
397: /**
398: * Gets the SASL realm to be used for DIGEST-MD5 authentication.
399: *
400: * @return        the name of the realm to use for SASL authentication.
401: *
402: * @since JavaMail 1.3.1
403: */
404: public synchronized String getSASLRealm() {
405:•        if (saslRealm == UNKNOWN) {
406:          saslRealm = session.getProperty("mail." + name + ".sasl.realm");
407:•         if (saslRealm == null)        // try old name
408:                 saslRealm = session.getProperty("mail." + name + ".saslrealm");
409:         }
410:         return saslRealm;
411: }
412:
413: /**
414: * Sets the SASL realm to be used for DIGEST-MD5 authentication.
415: *
416: * @param        saslRealm        the name of the realm to use for
417: *                                SASL authentication.
418: *
419: * @since JavaMail 1.3.1
420: */
421: public synchronized void setSASLRealm(String saslRealm) {
422:         this.saslRealm = saslRealm;
423: }
424:
425: /**
426: * Should SASL use the canonical host name?
427: *
428: * @return        true if SASL should use the canonical host name
429: *
430: * @since JavaMail 1.5.2
431: */
432: public synchronized boolean getUseCanonicalHostName() {
433:         return useCanonicalHostName;
434: }
435:
436: /**
437: * Set whether SASL should use the canonical host name.
438: *
439: * @param        useCanonicalHostName        should SASL use the canonical host name?
440: *
441: * @since JavaMail 1.5.2
442: */
443: public synchronized void setUseCanonicalHostName(
444:                                                 boolean useCanonicalHostName) {
445:         this.useCanonicalHostName = useCanonicalHostName;
446: }
447:
448: /**
449: * Get the list of SASL mechanisms to consider if SASL authentication
450: * is enabled. If the list is empty or null, all available SASL mechanisms
451: * are considered.
452: *
453: * @return        the array of SASL mechanisms to consider
454: *
455: * @since JavaMail 1.4.4
456: */
457: public synchronized String[] getSASLMechanisms() {
458:•        if (saslMechanisms == UNKNOWN_SA) {
459:          List<String> v = new ArrayList<>(5);
460:          String s = session.getProperty("mail." + name + ".sasl.mechanisms");
461:•         if (s != null && s.length() > 0) {
462:•                if (logger.isLoggable(Level.FINE))
463:                  logger.fine("SASL mechanisms allowed: " + s);
464:                 StringTokenizer st = new StringTokenizer(s, " ,");
465:•                while (st.hasMoreTokens()) {
466:                  String m = st.nextToken();
467:•                 if (m.length() > 0)
468:                         v.add(m);
469:                 }
470:          }
471:          saslMechanisms = new String[v.size()];
472:          v.toArray(saslMechanisms);
473:         }
474:•        if (saslMechanisms == null)
475:          return null;
476:         return saslMechanisms.clone();
477: }
478:
479: /**
480: * Set the list of SASL mechanisms to consider if SASL authentication
481: * is enabled. If the list is empty or null, all available SASL mechanisms
482: * are considered.
483: *
484: * @param        mechanisms        the array of SASL mechanisms to consider
485: *
486: * @since JavaMail 1.4.4
487: */
488: public synchronized void setSASLMechanisms(String[] mechanisms) {
489:•        if (mechanisms != null)
490:          mechanisms = mechanisms.clone();
491:         this.saslMechanisms = mechanisms;
492: }
493:
494: /**
495: * Gets the NTLM domain to be used for NTLM authentication.
496: *
497: * @return        the name of the domain to use for NTLM authentication.
498: *
499: * @since JavaMail 1.4.3
500: */
501: public synchronized String getNTLMDomain() {
502:•        if (ntlmDomain == UNKNOWN) {
503:          ntlmDomain =
504:                 session.getProperty("mail." + name + ".auth.ntlm.domain");
505:         }
506:         return ntlmDomain;
507: }
508:
509: /**
510: * Sets the NTLM domain to be used for NTLM authentication.
511: *
512: * @param        ntlmDomain        the name of the domain to use for
513: *                                NTLM authentication.
514: *
515: * @since JavaMail 1.4.3
516: */
517: public synchronized void setNTLMDomain(String ntlmDomain) {
518:         this.ntlmDomain = ntlmDomain;
519: }
520:
521: /**
522: * Should we report even successful sends by throwing an exception?
523: * If so, a <code>SendFailedException</code> will always be thrown and
524: * an {@link SMTPAddressSucceededException
525: * SMTPAddressSucceededException} will be included in the exception
526: * chain for each successful address, along with the usual
527: * {@link SMTPAddressFailedException
528: * SMTPAddressFailedException} for each unsuccessful address.
529: *
530: * @return        true if an exception will be thrown on successful sends.
531: *
532: * @since JavaMail 1.3.2
533: */
534: public synchronized boolean getReportSuccess() {
535:         return reportSuccess;
536: }
537:
538: /**
539: * Set whether successful sends should be reported by throwing
540: * an exception.
541: *
542: * @param        reportSuccess        should we throw an exception on success?
543: *
544: * @since JavaMail 1.3.2
545: */
546: public synchronized void setReportSuccess(boolean reportSuccess) {
547:         this.reportSuccess = reportSuccess;
548: }
549:
550: /**
551: * Should we use the STARTTLS command to secure the connection
552: * if the server supports it?
553: *
554: * @return        true if the STARTTLS command will be used
555: *
556: * @since JavaMail 1.3.2
557: */
558: public synchronized boolean getStartTLS() {
559:         return useStartTLS;
560: }
561:
562: /**
563: * Set whether the STARTTLS command should be used.
564: *
565: * @param        useStartTLS        should we use the STARTTLS command?
566: *
567: * @since JavaMail 1.3.2
568: */
569: public synchronized void setStartTLS(boolean useStartTLS) {
570:         this.useStartTLS = useStartTLS;
571: }
572:
573: /**
574: * Should we require the STARTTLS command to secure the connection?
575: *
576: * @return        true if the STARTTLS command will be required
577: *
578: * @since JavaMail 1.4.2
579: */
580: public synchronized boolean getRequireStartTLS() {
581:         return requireStartTLS;
582: }
583:
584: /**
585: * Set whether the STARTTLS command should be required.
586: *
587: * @param        requireStartTLS        should we require the STARTTLS command?
588: *
589: * @since JavaMail 1.4.2
590: */
591: public synchronized void setRequireStartTLS(boolean requireStartTLS) {
592:         this.requireStartTLS = requireStartTLS;
593: }
594:
595: /**
596: * Is this Transport using SSL to connect to the server?
597: *
598: * @return        true if using SSL
599: * @since        JavaMail 1.4.6
600: */
601: public synchronized boolean isSSL() {
602:         return serverSocket instanceof SSLSocket;
603: }
604:
605: /**
606: * Should we use the RSET command instead of the NOOP command
607: * in the @{link #isConnected isConnected} method?
608: *
609: * @return        true if RSET will be used
610: *
611: * @since JavaMail 1.4
612: */
613: public synchronized boolean getUseRset() {
614:         return useRset;
615: }
616:
617: /**
618: * Set whether the RSET command should be used instead of the
619: * NOOP command in the @{link #isConnected isConnected} method.
620: *
621: * @param        useRset        should we use the RSET command?
622: *
623: * @since JavaMail 1.4
624: */
625: public synchronized void setUseRset(boolean useRset) {
626:         this.useRset = useRset;
627: }
628:
629: /**
630: * Is the NOOP command required to return a response code
631: * of 250 to indicate success?
632: *
633: * @return        true if NOOP must return 250
634: *
635: * @since JavaMail 1.4.3
636: */
637: public synchronized boolean getNoopStrict() {
638:         return noopStrict;
639: }
640:
641: /**
642: * Set whether the NOOP command is required to return a response code
643: * of 250 to indicate success.
644: *
645: * @param        noopStrict is NOOP required to return 250?
646: *
647: * @since JavaMail 1.4.3
648: */
649: public synchronized void setNoopStrict(boolean noopStrict) {
650:         this.noopStrict = noopStrict;
651: }
652:
653: /**
654: * Return the last response we got from the server.
655: * A failed send is often followed by an RSET command,
656: * but the response from the RSET command is not saved.
657: * Instead, this returns the response from the command
658: * before the RSET command.
659: *
660: * @return        last response from server
661: *
662: * @since JavaMail 1.3.2
663: */
664: public synchronized String getLastServerResponse() {
665:         return lastServerResponse;
666: }
667:
668: /**
669: * Return the return code from the last response we got from the server.
670: *
671: * @return        return code from last response from server
672: *
673: * @since JavaMail 1.4.1
674: */
675: public synchronized int getLastReturnCode() {
676:         return lastReturnCode;
677: }
678:
679: /**
680: * Performs the actual protocol-specific connection attempt.
681: * Will attempt to connect to "localhost" if the host was null. <p>
682: *
683: * Unless mail.smtp.ehlo is set to false, we'll try to identify
684: * ourselves using the ESMTP command EHLO.
685: *
686: * If mail.smtp.auth is set to true, we insist on having a username
687: * and password, and will try to authenticate ourselves if the server
688: * supports the AUTH extension (RFC 2554).
689: *
690: * @param        host                 the name of the host to connect to
691: * @param        port                 the port to use (-1 means use default port)
692: * @param        user                 the name of the user to login as
693: * @param        password         the user's password
694: * @return        true if connection successful, false if authentication failed
695: * @exception MessagingException        for non-authentication failures
696: */
697: @Override
698: protected synchronized boolean protocolConnect(String host, int port,
699:                                 String user, String password)
700:                                 throws MessagingException {
701:         Properties props = session.getProperties();
702:
703:         // setting mail.smtp.auth to true enables attempts to use AUTH
704:         boolean useAuth = PropUtil.getBooleanProperty(props,
705:                                         "mail." + name + ".auth", false);
706:
707:         /*
708:          * If mail.smtp.auth is set, make sure we have a valid username
709:          * and password, even if we might not end up using it (e.g.,
710:          * because the server doesn't support ESMTP or doesn't support
711:          * the AUTH extension).
712:          */
713:•        if (useAuth && (user == null || password == null)) {
714:•         if (logger.isLoggable(Level.FINE)) {
715:                 logger.fine("need username and password for authentication");
716:                 logger.fine("protocolConnect returning false" +
717:                                 ", host=" + host +
718:                                 ", user=" + traceUser(user) +
719:                                 ", password=" + tracePassword(password));
720:          }
721:          return false;
722:         }
723:
724:         // setting mail.smtp.ehlo to false disables attempts to use EHLO
725:         boolean useEhlo = PropUtil.getBooleanProperty(props,
726:                                         "mail." + name + ".ehlo", true);
727:•        if (logger.isLoggable(Level.FINE))
728:          logger.fine("useEhlo " + useEhlo + ", useAuth " + useAuth);
729:
730:         /*
731:          * If port is not specified, set it to value of mail.smtp.port
732: * property if it exists, otherwise default to 25.
733:          */
734:• if (port == -1)
735:          port = PropUtil.getIntProperty(props,
736:                                         "mail." + name + ".port", -1);
737:• if (port == -1)
738:          port = defaultPort;
739:
740:•        if (host == null || host.length() == 0)
741:          host = "localhost";
742:
743:         /*
744:          * If anything goes wrong, we need to be sure
745:          * to close the connection.
746:          */
747:         boolean connected = false;
748:         try {
749:
750:•         if (serverSocket != null)
751:                 openServer();        // only happens from connect(socket)
752:          else
753:                 openServer(host, port);
754:
755:          boolean succeed = false;
756:•         if (useEhlo)
757:                 succeed = ehlo(getLocalHost());
758:•         if (!succeed)
759:                 helo(getLocalHost());
760:
761:•         if (useStartTLS || requireStartTLS) {
762:•                if (serverSocket instanceof SSLSocket) {
763:                  logger.fine("STARTTLS requested but already using SSL");
764:•                } else if (supportsExtension("STARTTLS")) {
765:                  startTLS();
766:                  /*
767:                  * Have to issue another EHLO to update list of extensions
768:                  * supported, especially authentication mechanisms.
769:                  * Don't know if this could ever fail, but we ignore
770:                  * failure.
771:                  */
772:                  ehlo(getLocalHost());
773:•                } else if (requireStartTLS) {
774:                  logger.fine("STARTTLS required but not supported");
775:                  throw new MessagingException(
776:                         "STARTTLS is required but " +
777:                         "host does not support STARTTLS");
778:                 }
779:          }
780:
781:•         if (allowutf8 && !supportsExtension("SMTPUTF8"))
782:                 logger.log(Level.INFO, "mail.mime.allowutf8 set " +
783:                          "but server doesn't advertise SMTPUTF8 support");
784:
785:•         if ((useAuth || (user != null && password != null)) &&
786:•                 (supportsExtension("AUTH") ||
787:•                 supportsExtension("AUTH=LOGIN"))) {
788:•                if (logger.isLoggable(Level.FINE))
789:                  logger.fine("protocolConnect login" +
790:                                 ", host=" + host +
791:                                 ", user=" + traceUser(user) +
792:                                 ", password=" + tracePassword(password));
793:                 connected = authenticate(user, password);
794:                 return connected;
795:          }
796:
797:          // we connected correctly
798:          connected = true;
799:          return true;
800:
801:         } finally {
802:          // if we didn't connect successfully,
803:          // make sure the connection is closed
804:•         if (!connected) {
805:                 try {
806:                  closeConnection();
807:                 } catch (MessagingException mex) {
808:                  // ignore it
809:                 }
810:          }
811:         }
812: }
813:
814: /**
815: * Authenticate to the server.
816: */
817: private boolean authenticate(String user, String passwd)
818:                                 throws MessagingException {
819:         // setting mail.smtp.auth.mechanisms controls which mechanisms will
820:         // be used, and in what order they'll be considered. only the first
821:         // match is used.
822:         String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
823:•        if (mechs == null)
824:          mechs = defaultAuthenticationMechanisms;
825:
826:         String authzid = getAuthorizationId();
827:•        if (authzid == null)
828:          authzid = user;
829:•        if (enableSASL) {
830:          logger.fine("Authenticate with SASL");
831:          try {
832:•                if (sasllogin(getSASLMechanisms(), getSASLRealm(), authzid,
833:                                 user, passwd)) {
834:                  return true;        // success
835:                 } else {
836:                  logger.fine("SASL authentication failed");
837:                  return false;
838:                 }
839:          } catch (UnsupportedOperationException ex) {
840:                 logger.log(Level.FINE, "SASL support failed", ex);
841:                 // if the SASL support fails, fall back to non-SASL
842:          }
843:         }
844:
845:•        if (logger.isLoggable(Level.FINE))
846:          logger.fine("Attempt to authenticate using mechanisms: " + mechs);
847:
848:         /*
849:          * Loop through the list of mechanisms supplied by the user
850:          * (or defaulted) and try each in turn. If the server supports
851:          * the mechanism and we have an authenticator for the mechanism,
852:          * and it hasn't been disabled, use it.
853:          */
854:         StringTokenizer st = new StringTokenizer(mechs);
855:•        while (st.hasMoreTokens()) {
856:          String m = st.nextToken();
857:          m = m.toUpperCase(Locale.ENGLISH);
858:          Authenticator a = authenticators.get(m);
859:•         if (a == null) {
860:                 logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
861:                 continue;
862:          }
863:
864:•         if (!supportsAuthentication(m)) {
865:                 logger.log(Level.FINE, "mechanism {0} not supported by server",
866:                                         m);
867:                 continue;
868:          }
869:
870:          /*
871:          * If using the default mechanisms, check if this one is disabled.
872:          */
873:•         if (mechs == defaultAuthenticationMechanisms) {
874:                 String dprop = "mail." + name + ".auth." +
875:                                  m.toLowerCase(Locale.ENGLISH) + ".disable";
876:                 boolean disabled = PropUtil.getBooleanProperty(
877:                                                 session.getProperties(),
878:•                                                dprop, !a.enabled());
879:•                if (disabled) {
880:•                 if (logger.isLoggable(Level.FINE))
881:                         logger.fine("mechanism " + m +
882:                                         " disabled by property: " + dprop);
883:                  continue;
884:                 }
885:          }
886:
887:          // only the first supported and enabled mechanism is used
888:          logger.log(Level.FINE, "Using mechanism {0}", m);
889:          return a.authenticate(host, authzid, user, passwd);
890:         }
891:
892:         // if no authentication mechanism found, fail
893:         throw new AuthenticationFailedException(
894:          "No authentication mechanisms supported by both server and client");
895: }
896:
897: /**
898: * Abstract base class for SMTP authentication mechanism implementations.
899: */
900: private abstract class Authenticator {
901:         protected int resp;        // the response code, used by subclasses
902:         private final String mech; // the mechanism name, set in the constructor
903:         private final boolean enabled; // is this mechanism enabled by default?
904:
905:         Authenticator(String mech) {
906:          this(mech, true);
907:         }
908:
909:         Authenticator(String mech, boolean enabled) {
910:          this.mech = mech.toUpperCase(Locale.ENGLISH);
911:          this.enabled = enabled;
912:         }
913:
914:         String getMechanism() {
915:          return mech;
916:         }
917:
918:         boolean enabled() {
919:          return enabled;
920:         }
921:
922:         /**
923:          * Start the authentication handshake by issuing the AUTH command.
924:          * Delegate to the doAuth method to do the mechanism-specific
925:          * part of the handshake.
926:          */
927:         boolean authenticate(String host, String authzid,
928:                         String user, String passwd) throws MessagingException {
929:          Throwable thrown = null;
930:          try {
931:                 // use "initial response" capability, if supported
932:                 String ir = getInitialResponse(host, authzid, user, passwd);
933:                 if (noauthdebug && isTracing()) {
934:                  logger.fine("AUTH " + mech + " command trace suppressed");
935:                  suspendTracing();
936:                 }
937:                 if (ir != null)
938:                  resp = simpleCommand("AUTH " + mech + " " +
939:                                          (ir.length() == 0 ? "=" : ir));
940:                 else
941:                  resp = simpleCommand("AUTH " + mech);
942:
943:                 /*
944:                  * A 530 response indicates that the server wants us to
945:                  * issue a STARTTLS command first. Do that and try again.
946:                  */
947:                 if (resp == 530) {
948:                  startTLS();
949:                  if (ir != null)
950:                         resp = simpleCommand("AUTH " + mech + " " + ir);
951:                  else
952:                         resp = simpleCommand("AUTH " + mech);
953:                 }
954:                 if (resp == 334)
955:                  doAuth(host, authzid, user, passwd);
956:          } catch (IOException ex) {        // should never happen, ignore
957:                 logger.log(Level.FINE, "AUTH " + mech + " failed", ex);
958:          } catch (Throwable t) {        // crypto can't be initialized?
959:                 logger.log(Level.FINE, "AUTH " + mech + " failed", t);
960:                 thrown = t;
961:          } finally {
962:                 if (noauthdebug && isTracing())
963:                  logger.fine("AUTH " + mech + " " +
964:                                  (resp == 235 ? "succeeded" : "failed"));
965:                 resumeTracing();
966:                 if (resp != 235) {
967:                  closeConnection();
968:                  if (thrown != null) {
969:                         if (thrown instanceof Error)
970:                          throw (Error)thrown;
971:                         if (thrown instanceof Exception)
972:                          throw new AuthenticationFailedException(
973:                                          getLastServerResponse(),
974:                                          (Exception)thrown);
975:                         assert false : "unknown Throwable";        // can't happen
976:                  }
977:                  throw new AuthenticationFailedException(
978:                                          getLastServerResponse());
979:                 }
980:          }
981:          return true;
982:         }
983:
984:         /**
985:          * Provide the initial response to use in the AUTH command,
986:          * or null if not supported. Subclasses that support the
987:          * initial response capability will override this method.
988:          */
989:         String getInitialResponse(String host, String authzid, String user,
990:                  String passwd) throws MessagingException, IOException {
991:          return null;
992:         }
993:
994:         abstract void doAuth(String host, String authzid, String user,
995:                  String passwd) throws MessagingException, IOException;
996: }
997:
998: /**
999: * Perform the authentication handshake for LOGIN authentication.
1000: */
1001: private class LoginAuthenticator extends Authenticator {
1002:         LoginAuthenticator() {
1003:          super("LOGIN");
1004:         }
1005:
1006:         @Override
1007:         void doAuth(String host, String authzid, String user, String passwd)
1008:                                  throws MessagingException, IOException {
1009:          // send username
1010:          resp = simpleCommand(Base64.getEncoder().encode(
1011:                                 user.getBytes(StandardCharsets.UTF_8)));
1012:          if (resp == 334) {
1013:                 // send passwd
1014:                 resp = simpleCommand(Base64.getEncoder().encode(
1015:                                 passwd.getBytes(StandardCharsets.UTF_8)));
1016:          }
1017:         }
1018: }
1019:
1020: /**
1021: * Perform the authentication handshake for PLAIN authentication.
1022: */
1023: private class PlainAuthenticator extends Authenticator {
1024:         PlainAuthenticator() {
1025:          super("PLAIN");
1026:         }
1027:
1028:         @Override
1029:         String getInitialResponse(String host, String authzid, String user,
1030:                         String passwd) throws MessagingException, IOException {
1031:          // return "authzid<NUL>user<NUL>passwd"
1032:          ByteArrayOutputStream bos = new ByteArrayOutputStream();
1033:          OutputStream b64os =
1034:                         new BASE64EncoderStream(bos, Integer.MAX_VALUE);
1035:          if (authzid != null)
1036:                 b64os.write(authzid.getBytes(StandardCharsets.UTF_8));
1037:          b64os.write(0);
1038:          b64os.write(user.getBytes(StandardCharsets.UTF_8));
1039:          b64os.write(0);
1040:          b64os.write(passwd.getBytes(StandardCharsets.UTF_8));
1041:          b64os.flush();         // complete the encoding
1042:
1043:          return ASCIIUtility.toString(bos.toByteArray());
1044:         }
1045:
1046:         @Override
1047:         void doAuth(String host, String authzid, String user, String passwd)
1048:                                  throws MessagingException, IOException {
1049:          // should never get here
1050:          throw new AuthenticationFailedException("PLAIN asked for more");
1051:         }
1052: }
1053:
1054: /**
1055: * Perform the authentication handshake for DIGEST-MD5 authentication.
1056: */
1057: private class DigestMD5Authenticator extends Authenticator {
1058:         private DigestMD5 md5support;        // only create if needed
1059:
1060:         DigestMD5Authenticator() {
1061:          super("DIGEST-MD5");
1062:         }
1063:
1064:         private synchronized DigestMD5 getMD5() {
1065:          if (md5support == null)
1066:                 md5support = new DigestMD5(logger);
1067:          return md5support;
1068:         }
1069:
1070:         @Override
1071:         void doAuth(String host, String authzid, String user, String passwd)
1072:                                  throws MessagingException, IOException {
1073:          DigestMD5 md5 = getMD5();
1074:          assert md5 != null;
1075:
1076:          byte[] b = md5.authClient(host, user, passwd, getSASLRealm(),
1077:                                         getLastServerResponse());
1078:          resp = simpleCommand(b);
1079:          if (resp == 334) { // client authenticated by server
1080:                 if (!md5.authServer(getLastServerResponse())) {
1081:                  // server NOT authenticated by client !!!
1082:                  resp = -1;
1083:                 } else {
1084:                  // send null response
1085:                  resp = simpleCommand(new byte[0]);
1086:                 }
1087:          }
1088:         }
1089: }
1090:
1091: /**
1092: * Perform the authentication handshake for NTLM authentication.
1093: */
1094: private class NtlmAuthenticator extends Authenticator {
1095:         private Ntlm ntlm;
1096:
1097:         NtlmAuthenticator() {
1098:          super("NTLM");
1099:         }
1100:
1101:         @Override
1102:         String getInitialResponse(String host, String authzid, String user,
1103:                 String passwd) throws MessagingException, IOException {
1104:          ntlm = new Ntlm(getNTLMDomain(), getLocalHost(),
1105:                                 user, passwd, logger);
1106:
1107:          int flags = PropUtil.getIntProperty(
1108:                  session.getProperties(),
1109:                  "mail." + name + ".auth.ntlm.flags", 0);
1110:          boolean v2 = PropUtil.getBooleanProperty(
1111:                  session.getProperties(),
1112:                  "mail." + name + ".auth.ntlm.v2", true);
1113:
1114:          String type1 = ntlm.generateType1Msg(flags, v2);
1115:          return type1;
1116:         }
1117:
1118:         @Override
1119:         void doAuth(String host, String authzid, String user, String passwd)
1120:                 throws MessagingException, IOException {
1121:          assert ntlm != null;
1122:          String type3 = ntlm.generateType3Msg(
1123:                  getLastServerResponse().substring(4).trim());
1124:
1125:          resp = simpleCommand(type3);
1126:         }
1127: }
1128:
1129: /**
1130: * Perform the authentication handshake for XOAUTH2 authentication.
1131: */
1132: private class OAuth2Authenticator extends Authenticator {
1133:
1134:         OAuth2Authenticator() {
1135:          super("XOAUTH2", false);        // disabled by default
1136:         }
1137:
1138:         @Override
1139:         String getInitialResponse(String host, String authzid, String user,
1140:                 String passwd) throws MessagingException, IOException {
1141:          String resp = "user=" + user + "\001auth=Bearer " +
1142:                          passwd + "\001\001";
1143:          byte[] b = Base64.getEncoder().encode(
1144:                                         resp.getBytes(StandardCharsets.UTF_8));
1145:          return ASCIIUtility.toString(b);
1146:         }
1147:
1148:         @Override
1149:         void doAuth(String host, String authzid, String user, String passwd)
1150:                 throws MessagingException, IOException {
1151:          // should never get here
1152:          throw new AuthenticationFailedException("OAUTH2 asked for more");
1153:         }
1154: }
1155:
1156: /**
1157: * SASL-based login.
1158: *
1159: * @param        allowed        the allowed SASL mechanisms
1160: * @param        realm        the SASL realm
1161: * @param        authzid        the authorization ID
1162: * @param        u        the user name for authentication
1163: * @param        p        the password for authentication
1164: * @return                true for success
1165: * @exception        MessagingException for failures
1166: */
1167: private boolean sasllogin(String[] allowed, String realm, String authzid,
1168:                                 String u, String p) throws MessagingException {
1169:         String serviceHost;
1170:•        if (useCanonicalHostName)
1171:          serviceHost = serverSocket.getInetAddress().getCanonicalHostName();
1172:         else
1173:          serviceHost = host;
1174:•        if (saslAuthenticator == null) {
1175:          try {
1176:                 Class<?> sac = Class.forName(
1177: "org.eclipse.angus.mail.smtp.SMTPSaslAuthenticator");
1178:                 Constructor<?> c = sac.getConstructor(new Class<?>[] {
1179:                                         SMTPTransport.class,
1180:                                         String.class,
1181:                                         Properties.class,
1182:                                         MailLogger.class,
1183:                                         String.class
1184:                                         });
1185:                 saslAuthenticator = (SaslAuthenticator)c.newInstance(
1186:                                         new Object[] {
1187:                                         this,
1188:                                         name,
1189:                                         session.getProperties(),
1190:                                         logger,
1191:                                         serviceHost
1192:                                         });
1193:          } catch (Exception ex) {
1194:                 logger.log(Level.FINE, "Can't load SASL authenticator", ex);
1195:                 // probably because we're running on a system without SASL
1196:                 return false;        // not authenticated, try without SASL
1197:          }
1198:         }
1199:
1200:         // were any allowed mechanisms specified?
1201:         List<String> v;
1202:•        if (allowed != null && allowed.length > 0) {
1203:          // remove anything not supported by the server
1204:          v = new ArrayList<>(allowed.length);
1205:•         for (int i = 0; i < allowed.length; i++)
1206:•                if (supportsAuthentication(allowed[i]))        // XXX - case must match
1207:                  v.add(allowed[i]);
1208:         } else {
1209:          // everything is allowed
1210:          v = new ArrayList<>();
1211:•         if (extMap != null) {
1212:                 String a = extMap.get("AUTH");
1213:•                if (a != null) {
1214:                  StringTokenizer st = new StringTokenizer(a);
1215:•                 while (st.hasMoreTokens())
1216:                         v.add(st.nextToken());
1217:                 }
1218:          }
1219:         }
1220:         String[] mechs = v.toArray(new String[v.size()]);
1221:         try {
1222:•         if (noauthdebug && isTracing()) {
1223:                 logger.fine("SASL AUTH command trace suppressed");
1224:                 suspendTracing();
1225:          }
1226:          return saslAuthenticator.authenticate(mechs, realm, authzid, u, p);
1227:         } finally {
1228:          resumeTracing();
1229:         }
1230: }
1231:
1232: /**
1233: * Send the Message to the specified list of addresses.<p>
1234: *
1235: * If all the <code>addresses</code> succeed the SMTP check
1236: * using the <code>RCPT TO:</code> command, we attempt to send the message.
1237: * A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
1238: * successful submission of a message to the SMTP host.<p>
1239: *
1240: * If some of the <code>addresses</code> fail the SMTP check,
1241: * and the <code>mail.smtp.sendpartial</code> property is not set,
1242: * sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
1243: * is fired containing the valid and invalid addresses. The
1244: * SendFailedException is also thrown. <p>
1245: *
1246: * If some of the <code>addresses</code> fail the SMTP check,
1247: * and the <code>mail.smtp.sendpartial</code> property is set to true,
1248: * the message is sent. The TransportEvent of type
1249: * MESSAGE_PARTIALLY_DELIVERED
1250: * is fired containing the valid and invalid addresses. The
1251: * SMTPSendFailedException is also thrown. <p>
1252: *
1253: * MessagingException is thrown if the message can't write out
1254: * an RFC822-compliant stream using its <code>writeTo</code> method. <p>
1255: *
1256: * @param message        The MimeMessage to be sent
1257: * @param addresses        List of addresses to send this message to
1258: * @see                 jakarta.mail.event.TransportEvent
1259: * @exception SMTPSendFailedException if the send failed because of
1260: *                        an SMTP command error
1261: * @exception SendFailedException if the send failed because of
1262: *                        invalid addresses.
1263: * @exception MessagingException if the connection is dead
1264: * or not in the connected state or if the message is
1265: * not a MimeMessage.
1266: */
1267: @Override
1268: public synchronized void sendMessage(Message message, Address[] addresses)
1269:                  throws MessagingException, SendFailedException {
1270:
1271:•        sendMessageStart(message != null ? message.getSubject() : "");
1272:         checkConnected();
1273:
1274:         // check if the message is a valid MIME/RFC822 message and that
1275:         // it has all valid InternetAddresses; fail if not
1276:• if (!(message instanceof MimeMessage)) {
1277:          logger.fine("Can only send RFC822 msgs");
1278:          throw new MessagingException("SMTP can only send RFC822 messages");
1279:         }
1280:• if (addresses == null || addresses.length == 0) {
1281: throw new SendFailedException("No recipient addresses");
1282: }
1283:•        for (int i = 0; i < addresses.length; i++) {
1284:•         if (!(addresses[i] instanceof InternetAddress)) {
1285:                 throw new MessagingException(addresses[i] +
1286:                                          " is not an InternetAddress");
1287:          }
1288:         }
1289:
1290:         this.message = (MimeMessage)message;
1291:         this.addresses = addresses;
1292:         validUnsentAddr = addresses;        // until we know better
1293:         expandGroups();
1294:
1295:         boolean use8bit = false;
1296:•        if (message instanceof SMTPMessage)
1297:          use8bit = ((SMTPMessage)message).getAllow8bitMIME();
1298:•        if (!use8bit)
1299:          use8bit = PropUtil.getBooleanProperty(session.getProperties(),
1300:                                 "mail." + name + ".allow8bitmime", false);
1301:•        if (logger.isLoggable(Level.FINE))
1302:          logger.fine("use8bit " + use8bit);
1303:•        if (use8bit && supportsExtension("8BITMIME")) {
1304:•         if (convertTo8Bit(this.message)) {
1305:                 // in case we made any changes, save those changes
1306:                 // XXX - this will change the Message-ID
1307:                 try {
1308:                  this.message.saveChanges();
1309:                 } catch (MessagingException mex) {
1310:                  // ignore it
1311:                 }
1312:          }
1313:         }
1314:
1315:         try {
1316:          mailFrom();
1317:          rcptTo();
1318:•         if (chunkSize > 0 && supportsExtension("CHUNKING")) {
1319:                 /*
1320:                  * Use BDAT to send the data in chunks.
1321:                  * Note that even though the BDAT command is able to send
1322:                  * messages that contain binary data, we can't use it to
1323:                  * do that because a) we still need to canonicalize the
1324:                  * line terminators for text data, which we can't tell apart
1325:                  * from the message content, and b) the message content is
1326:                  * encoded before we even know that we can use BDAT.
1327:                  */
1328:                 this.message.writeTo(bdat(), ignoreList);
1329:                 finishBdat();
1330:          } else {
1331:                 this.message.writeTo(data(), ignoreList);
1332:                 finishData();
1333:          }
1334:•         if (sendPartiallyFailed) {
1335:                 // throw the exception,
1336:                 // fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
1337:                 logger.fine("Sending partially failed " +
1338:                         "because of invalid destination addresses");
1339:                 notifyTransportListeners(
1340:                         TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
1341:                         validSentAddr, validUnsentAddr, invalidAddr,
1342:                         this.message);
1343:
1344:                 throw new SMTPSendFailedException(".", lastReturnCode,
1345:                                 lastServerResponse, exception,
1346:                                 validSentAddr, validUnsentAddr, invalidAddr);
1347:          }
1348:          logger.fine("message successfully delivered to mail server");
1349:          notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
1350:                                  validSentAddr, validUnsentAddr,
1351:                                  invalidAddr, this.message);
1352:         } catch (MessagingException mex) {
1353:          logger.log(Level.FINE, "MessagingException while sending", mex);
1354:          // the MessagingException might be wrapping an IOException
1355:•         if (mex.getNextException() instanceof IOException) {
1356:                 // if we catch an IOException, it means that we want
1357:                 // to drop the connection so that the message isn't sent
1358:                 logger.fine("nested IOException, closing");
1359:                 try {
1360:                  closeConnection();
1361:                 } catch (MessagingException cex) { /* ignore it */ }
1362:          }
1363:          addressesFailed();
1364:          notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
1365:                                  validSentAddr, validUnsentAddr,
1366:                                  invalidAddr, this.message);
1367:
1368:          throw mex;
1369:         } catch (IOException ex) {
1370:          logger.log(Level.FINE, "IOException while sending, closing", ex);
1371:          // if we catch an IOException, it means that we want
1372:          // to drop the connection so that the message isn't sent
1373:          try {
1374:                 closeConnection();
1375:          } catch (MessagingException mex) { /* ignore it */ }
1376:          addressesFailed();
1377:          notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
1378:                                  validSentAddr, validUnsentAddr,
1379:                                  invalidAddr, this.message);
1380:
1381:          throw new MessagingException("IOException while sending message",
1382:                                          ex);
1383:         } finally {
1384:          // no reason to keep this data around
1385:          validSentAddr = validUnsentAddr = invalidAddr = null;
1386:          this.addresses = null;
1387:          this.message = null;
1388:          this.exception = null;
1389:          sendPartiallyFailed = false;
1390:          notificationDone = false;        // reset for next send
1391:         }
1392:         sendMessageEnd();
1393: }
1394:
1395: /**
1396: * The send failed, fix the address arrays to report the failure correctly.
1397: */
1398: private void addressesFailed() {
1399:•        if (validSentAddr != null) {
1400:•         if (validUnsentAddr != null) {
1401:                 Address newa[] =
1402:                  new Address[validSentAddr.length + validUnsentAddr.length];
1403:                 System.arraycopy(validSentAddr, 0,
1404:                         newa, 0, validSentAddr.length);
1405:                 System.arraycopy(validUnsentAddr, 0,
1406:                         newa, validSentAddr.length, validUnsentAddr.length);
1407:                 validSentAddr = null;
1408:                 validUnsentAddr = newa;
1409:          } else {
1410:                 validUnsentAddr = validSentAddr;
1411:                 validSentAddr = null;
1412:          }
1413:         }
1414: }
1415:
1416: /**
1417: * Close the Transport and terminate the connection to the server.
1418: */
1419: @Override
1420: public synchronized void close() throws MessagingException {
1421:•        if (!super.isConnected()) // Already closed.
1422:          return;
1423:         try {
1424:•         if (serverSocket != null) {
1425:                 sendCommand("QUIT");
1426:•                if (quitWait) {
1427:                  int resp = readServerResponse();
1428:•                 if (resp != 221 && resp != -1 &&
1429:•                         logger.isLoggable(Level.FINE))
1430:                         logger.fine("QUIT failed with " + resp);
1431:                 }
1432:          }
1433:         } finally {
1434:          closeConnection();
1435:         }
1436: }
1437:
1438: private void closeConnection() throws MessagingException {
1439:         try {
1440:•         if (serverSocket != null)
1441:                 serverSocket.close();
1442:         } catch (IOException ioex) {         // shouldn't happen
1443:          throw new MessagingException("Server Close Failed", ioex);
1444:         } finally {
1445:          serverSocket = null;
1446:          serverOutput = null;
1447:          serverInput = null;
1448:          lineInputStream = null;
1449:•         if (super.isConnected())        // only notify if already connected
1450:                 super.close();
1451:         }
1452: }
1453:
1454: /**
1455: * Check whether the transport is connected. Override superclass
1456: * method, to actually ping our server connection.
1457: */
1458: @Override
1459: public synchronized boolean isConnected() {
1460:•        if (!super.isConnected())
1461:          // if we haven't been connected at all, don't bother with NOOP
1462:          return false;
1463:
1464:         try {
1465:          // sendmail may respond slowly to NOOP after many requests
1466:          // so if mail.smtp.userset is set we use RSET instead of NOOP.
1467:•         if (useRset)
1468:                 sendCommand("RSET");
1469:          else
1470:                 sendCommand("NOOP");
1471:          int resp = readServerResponse();
1472:
1473:          /*
1474:          * NOOP should return 250 on success, however, SIMS 3.2 returns
1475:          * 200, so we work around it.
1476:          *
1477:          * Hotmail didn't used to implement the NOOP command at all so
1478:          * assume any kind of response means we're still connected.
1479:          * That is, any response except 421, which means the server
1480:          * is shutting down the connection.
1481:          *
1482:          * Some versions of Exchange return 451 instead of 421 when
1483:          * timing out a connection.
1484:          *
1485:          * Argh!
1486:          *
1487:          * If mail.smtp.noop.strict is set to false, be tolerant of
1488:          * servers that return the wrong response code for success.
1489:          */
1490:•         if (resp >= 0 && (noopStrict ? resp == 250 : resp != 421)) {
1491:                 return true;
1492:          } else {
1493:                 try {
1494:                  closeConnection();
1495:                 } catch (MessagingException mex) {
1496:                  // ignore it
1497:                 }
1498:                 return false;
1499:          }
1500:         } catch (Exception ex) {
1501:          try {
1502:                 closeConnection();
1503:          } catch (MessagingException mex) {
1504:                 // ignore it
1505:          }
1506:          return false;
1507:         }
1508: }
1509:
1510: /**
1511: * Notify all TransportListeners. Keep track of whether notification
1512: * has been done so as to only notify once per send.
1513: *
1514: * @since        JavaMail 1.4.2
1515: */
1516: @Override
1517: protected void notifyTransportListeners(int type, Address[] validSent,
1518:                                          Address[] validUnsent,
1519:                                          Address[] invalid, Message msg) {
1520:
1521:•        if (!notificationDone) {
1522:          super.notifyTransportListeners(type, validSent, validUnsent,
1523:                 invalid, msg);
1524:          notificationDone = true;
1525:         }
1526: }
1527:
1528: /**
1529: * Expand any group addresses.
1530: */
1531: private void expandGroups() {
1532:         List<Address> groups = null;
1533:•        for (int i = 0; i < addresses.length; i++) {
1534:          InternetAddress a = (InternetAddress)addresses[i];
1535:•         if (a.isGroup()) {
1536:•                if (groups == null) {
1537:                  // first group, catch up with where we are
1538:                  groups = new ArrayList<>();
1539:•                 for (int k = 0; k < i; k++)
1540:                         groups.add(addresses[k]);
1541:                 }
1542:                 // parse it and add each individual address
1543:                 try {
1544:                  InternetAddress[] ia = a.getGroup(true);
1545:•                 if (ia != null) {
1546:•                        for (int j = 0; j < ia.length; j++)
1547:                          groups.add(ia[j]);
1548:                  } else
1549:                         groups.add(a);
1550:                 } catch (ParseException pex) {
1551:                  // parse failed, add the whole thing
1552:                  groups.add(a);
1553:                 }
1554:          } else {
1555:                 // if we've started accumulating a list, add this to it
1556:•                if (groups != null)
1557:                  groups.add(a);
1558:          }
1559:         }
1560:
1561:         // if we have a new list, convert it back to an array
1562:•        if (groups != null) {
1563:          InternetAddress[] newa = new InternetAddress[groups.size()];
1564:          groups.toArray(newa);
1565:          addresses = newa;
1566:         }
1567: }
1568:
1569: /**
1570: * If the Part is a text part and has a Content-Transfer-Encoding
1571: * of "quoted-printable" or "base64", and it obeys the rules for
1572: * "8bit" encoding, change the encoding to "8bit". If the part is
1573: * a multipart, recursively process all its parts.
1574: *
1575: * @return        true        if any changes were made
1576: *
1577: * XXX - This is really quite a hack.
1578: */
1579: private boolean convertTo8Bit(MimePart part) {
1580:         boolean changed = false;
1581:         try {
1582:•         if (part.isMimeType("text/*")) {
1583:                 String enc = part.getEncoding();
1584:•                if (enc != null && (enc.equalsIgnoreCase(EncoderTypes.QUOTED_PRINTABLE_ENCODER.getEncoder()) ||
1585:•                 enc.equalsIgnoreCase(EncoderTypes.BASE_64.getEncoder()))) {
1586:                  InputStream is = null;
1587:                  try {
1588:                         is = part.getInputStream();
1589:•                        if (is8Bit(is)) {
1590:                          /*
1591:                          * If the message was created using an InputStream
1592:                          * then we have to extract the content as an object
1593:                          * and set it back as an object so that the content
1594:                          * will be re-encoded.
1595:                          *
1596:                          * If the message was not created using an
1597:                          * InputStream, the following should have no effect.
1598:                          */
1599:                          part.setContent(part.getContent(),
1600:                                          part.getContentType());
1601:                          part.setHeader("Content-Transfer-Encoding", EncoderTypes.BIT8_ENCODER.getEncoder());
1602:                          changed = true;
1603:                         }
1604:                  } finally {
1605:•                        if (is != null) {
1606:                          try {
1607:                                 is.close();
1608:                          } catch (IOException ex2) {
1609:                                 // ignore it
1610:                          }
1611:                         }
1612:                  }
1613:                 }
1614:•         } else if (part.isMimeType("multipart/*")) {
1615:                 MimeMultipart mp = (MimeMultipart)part.getContent();
1616:                 int count = mp.getCount();
1617:•                for (int i = 0; i < count; i++) {
1618:•                 if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
1619:                         changed = true;
1620:                 }
1621:          }
1622:         } catch (IOException ioex) {
1623:          // any exception causes us to give up
1624:         } catch (MessagingException mex) {
1625:          // any exception causes us to give up
1626:         }
1627:         return changed;
1628: }
1629:
1630: /**
1631: * Check whether the data in the given InputStream follows the
1632: * rules for 8bit text. Lines have to be 998 characters or less
1633: * and no NULs are allowed. CR and LF must occur in pairs but we
1634: * don't check that because we assume this is text and we convert
1635: * all CR/LF combinations into canonical CRLF later.
1636: */
1637: private boolean is8Bit(InputStream is) {
1638:         int b;
1639:         int linelen = 0;
1640:         boolean need8bit = false;
1641:         try {
1642:•         while ((b = is.read()) >= 0) {
1643:                 b &= 0xff;
1644:•                if (b == '\r' || b == '\n')
1645:                  linelen = 0;
1646:•                else if (b == 0)
1647:                  return false;
1648:                 else {
1649:                  linelen++;
1650:•                 if (linelen > 998)        // 1000 - CRLF
1651:                         return false;
1652:                 }
1653:•                if (b > 0x7f)
1654:                  need8bit = true;
1655:          }
1656:         } catch (IOException ex) {
1657:          return false;
1658:         }
1659:•        if (need8bit)
1660:          logger.fine("found an 8bit part");
1661:         return need8bit;
1662: }
1663:
1664: @Override
1665: protected void finalize() throws Throwable {
1666:         try {
1667:          closeConnection();
1668:         } catch (MessagingException mex) {
1669:          // ignore it
1670:         } finally {
1671:          super.finalize();
1672:         }
1673: }
1674:
1675: ///////////////////// smtp stuff ///////////////////////
1676: private BufferedInputStream serverInput;
1677: private LineInputStream lineInputStream;
1678: private OutputStream serverOutput;
1679: private Socket serverSocket;
1680: private TraceInputStream traceInput;
1681: private TraceOutputStream traceOutput;
1682:
1683: /////// smtp protocol //////
1684:
1685: /**
1686: * Issue the <code>HELO</code> command.
1687: *
1688: * @param        domain        our domain
1689: * @exception        MessagingException for failures
1690: * @since JavaMail 1.4.1
1691: */
1692: protected void helo(String domain) throws MessagingException {
1693:•        if (domain != null)
1694:          issueCommand("HELO " + domain, 250);
1695:         else
1696:          issueCommand("HELO", 250);
1697: }
1698:
1699: /**
1700: * Issue the <code>EHLO</code> command.
1701: * Collect the returned list of service extensions.
1702: *
1703: * @param        domain        our domain
1704: * @return                true if command succeeds
1705: * @exception        MessagingException for failures
1706: * @since JavaMail 1.4.1
1707: */
1708: protected boolean ehlo(String domain) throws MessagingException {
1709:         String cmd;
1710:•        if (domain != null)
1711:          cmd = "EHLO " + domain;
1712:         else
1713:          cmd = "EHLO";
1714:         sendCommand(cmd);
1715:         int resp = readServerResponse();
1716:•        if (resp == 250) {
1717:          // extract the supported service extensions
1718:          BufferedReader rd =
1719:                 new BufferedReader(new StringReader(lastServerResponse));
1720:          String line;
1721:          extMap = new Hashtable<>();
1722:          try {
1723:                 boolean first = true;
1724:•                while ((line = rd.readLine()) != null) {
1725:•                 if (first) {        // skip first line which is the greeting
1726:                         first = false;
1727:                         continue;
1728:                  }
1729:•                 if (line.length() < 5)
1730:                         continue;                // shouldn't happen
1731:                  line = line.substring(4);        // skip response code
1732:                  int i = line.indexOf(' ');
1733:                  String arg = "";
1734:•                 if (i > 0) {
1735:                         arg = line.substring(i + 1);
1736:                         line = line.substring(0, i);
1737:                  }
1738:•                 if (logger.isLoggable(Level.FINE))
1739:                         logger.fine("Found extension \"" +
1740:                                          line + "\", arg \"" + arg + "\"");
1741:                  extMap.put(line.toUpperCase(Locale.ENGLISH), arg);
1742:                 }
1743:          } catch (IOException ex) { }        // can't happen
1744:         }
1745:•        return resp == 250;
1746: }
1747:
1748: /**
1749: * Issue the <code>MAIL FROM:</code> command to start sending a message. <p>
1750: *
1751: * Gets the sender's address in the following order:
1752: * <ol>
1753: * <li>SMTPMessage.getEnvelopeFrom()</li>
1754: * <li>mail.smtp.from property</li>
1755: * <li>From: header in the message</li>
1756: * <li>System username using the
1757: * InternetAddress.getLocalAddress() method</li>
1758: * </ol>
1759: *
1760: * @exception        MessagingException for failures
1761: * @since JavaMail 1.4.1
1762: */
1763: protected void mailFrom() throws MessagingException {
1764:         String from = null;
1765:•        if (message instanceof SMTPMessage)
1766:          from = ((SMTPMessage)message).getEnvelopeFrom();
1767:•        if (from == null || from.length() <= 0)
1768:          from = session.getProperty("mail." + name + ".from");
1769:•        if (from == null || from.length() <= 0) {
1770:          Address[] fa;
1771:          Address me;
1772:•         if (message != null && (fa = message.getFrom()) != null &&
1773:                  fa.length > 0)
1774:                 me = fa[0];
1775:          else
1776:                 me = InternetAddress.getLocalAddress(session);
1777:
1778:•         if (me != null)
1779:                 from = ((InternetAddress)me).getAddress();
1780:          else
1781:                 throw new MessagingException(
1782:                                         "can't determine local email address");
1783:         }
1784:
1785:         String cmd = "MAIL FROM:" + normalizeAddress(from);
1786:
1787:•        if (allowutf8 && supportsExtension("SMTPUTF8"))
1788:          cmd += " SMTPUTF8";
1789:
1790:         // request delivery status notification?
1791:•        if (supportsExtension("DSN")) {
1792:          String ret = null;
1793:•         if (message instanceof SMTPMessage)
1794:                 ret = ((SMTPMessage)message).getDSNRet();
1795:•         if (ret == null)
1796:                 ret = session.getProperty("mail." + name + ".dsn.ret");
1797:          // XXX - check for legal syntax?
1798:•         if (ret != null)
1799:                 cmd += " RET=" + ret;
1800:         }
1801:
1802:         /*
1803:          * If an RFC 2554 submitter has been specified, and the server
1804:          * supports the AUTH extension, include the AUTH= element on
1805:          * the MAIL FROM command.
1806:          */
1807:•        if (supportsExtension("AUTH")) {
1808:          String submitter = null;
1809:•         if (message instanceof SMTPMessage)
1810:                 submitter = ((SMTPMessage)message).getSubmitter();
1811:•         if (submitter == null)
1812:                 submitter = session.getProperty("mail." + name + ".submitter");
1813:          // XXX - check for legal syntax?
1814:•         if (submitter != null) {
1815:                 try {
1816:•                 String s = xtext(submitter,
1817:•                                 allowutf8 && supportsExtension("SMTPUTF8"));
1818:                  cmd += " AUTH=" + s;
1819:                 } catch (IllegalArgumentException ex) {
1820:•                 if (logger.isLoggable(Level.FINE))
1821:                         logger.log(Level.FINE, "ignoring invalid submitter: " +
1822:                          submitter, ex);
1823:                 }
1824:          }
1825:         }
1826:
1827:         /*
1828:          * Have any extensions to the MAIL command been specified?
1829:          */
1830:         String ext = null;
1831:•        if (message instanceof SMTPMessage)
1832:          ext = ((SMTPMessage)message).getMailExtension();
1833:•        if (ext == null)
1834:          ext = session.getProperty("mail." + name + ".mailextension");
1835:•        if (ext != null && ext.length() > 0)
1836:          cmd += " " + ext;
1837:
1838:         try {
1839:          issueSendCommand(cmd, 250);
1840:         } catch (SMTPSendFailedException ex) {
1841:          int retCode = ex.getReturnCode();
1842:•         switch (retCode) {
1843:          case 550: case 553: case 503: case 551: case 501:
1844:                 // given address is invalid
1845:                 try {
1846:                  ex.setNextException(new SMTPSenderFailedException(
1847:                         new InternetAddress(from), cmd,
1848:                         retCode, ex.getMessage()));
1849:                 } catch (AddressException aex) {
1850:                  // oh well...
1851:                 }
1852:                 break;
1853:          default:
1854:                 break;
1855:          }
1856:          throw ex;
1857:         }
1858: }
1859:
1860: /**
1861: * Sends each address to the SMTP host using the <code>RCPT TO:</code>
1862: * command and copies the address either into
1863: * the validSentAddr or invalidAddr arrays.
1864: * Sets the <code>sendFailed</code>
1865: * flag to true if any addresses failed.
1866: *
1867: * @exception        MessagingException for failures
1868: * @since JavaMail 1.4.1
1869: */
1870: /*
1871: * success/failure/error possibilities from the RCPT command
1872: * from rfc821, section 4.3
1873: * S: 250, 251
1874: * F: 550, 551, 552, 553, 450, 451, 452
1875: * E: 500, 501, 503, 421
1876: *
1877: * and how we map the above error/failure conditions to valid/invalid
1878: * address lists that are reported in the thrown exception:
1879: * invalid addr: 550, 501, 503, 551, 553
1880: * valid addr: 552 (quota), 450, 451, 452 (quota), 421 (srvr abort)
1881: */
1882: protected void rcptTo() throws MessagingException {
1883:         List<InternetAddress> valid = new ArrayList<>();
1884:         List<InternetAddress> validUnsent = new ArrayList<>();
1885:         List<InternetAddress> invalid = new ArrayList<>();
1886:         int retCode = -1;
1887:         MessagingException mex = null;
1888:         boolean sendFailed = false;
1889:         MessagingException sfex = null;
1890:         validSentAddr = validUnsentAddr = invalidAddr = null;
1891:         boolean sendPartial = false;
1892:•        if (message instanceof SMTPMessage)
1893:          sendPartial = ((SMTPMessage)message).getSendPartial();
1894:•        if (!sendPartial)
1895:          sendPartial = PropUtil.getBooleanProperty(session.getProperties(),
1896:                                         "mail." + name + ".sendpartial", false);
1897:•        if (sendPartial)
1898:          logger.fine("sendPartial set");
1899:
1900:         boolean dsn = false;
1901:         String notify = null;
1902:•        if (supportsExtension("DSN")) {
1903:•         if (message instanceof SMTPMessage)
1904:                 notify = ((SMTPMessage)message).getDSNNotify();
1905:•         if (notify == null)
1906:                 notify = session.getProperty("mail." + name + ".dsn.notify");
1907:          // XXX - check for legal syntax?
1908:•         if (notify != null)
1909:                 dsn = true;
1910:         }
1911:
1912:         // try the addresses one at a time
1913:•        for (int i = 0; i < addresses.length; i++) {
1914:
1915:          sfex = null;
1916:          InternetAddress ia = (InternetAddress)addresses[i];
1917:          String cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
1918:•         if (dsn)
1919:                 cmd += " NOTIFY=" + notify;
1920:          // send the addresses to the SMTP server
1921:          sendCommand(cmd);
1922:          // check the server's response for address validity
1923:          retCode = readServerResponse();
1924:•         switch (retCode) {
1925:          case 250: case 251:
1926:                 valid.add(ia);
1927:•                if (!reportSuccess)
1928:                  break;
1929:
1930:                 // user wants exception even when successful, including
1931:                 // details of the return code
1932:
1933:                 // create and chain the exception
1934:                 sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
1935:                                                         lastServerResponse);
1936:•                if (mex == null)
1937:                  mex = sfex;
1938:                 else
1939:                  mex.setNextException(sfex);
1940:                 break;
1941:
1942:          case 550: case 553: case 503: case 551: case 501:
1943:                 // given address is invalid
1944:•                if (!sendPartial)
1945:                  sendFailed = true;
1946:                 invalid.add(ia);
1947:                 // create and chain the exception
1948:                 sfex = new SMTPAddressFailedException(ia, cmd, retCode,
1949:                                                         lastServerResponse);
1950:•                if (mex == null)
1951:                  mex = sfex;
1952:                 else
1953:                  mex.setNextException(sfex);
1954:                 break;
1955:
1956:          case 552: case 450: case 451: case 452:
1957:                 // given address is valid
1958:•                if (!sendPartial)
1959:                  sendFailed = true;
1960:                 validUnsent.add(ia);
1961:                 // create and chain the exception
1962:                 sfex = new SMTPAddressFailedException(ia, cmd, retCode,
1963:                                                         lastServerResponse);
1964:•                if (mex == null)
1965:                  mex = sfex;
1966:                 else
1967:                  mex.setNextException(sfex);
1968:                 break;
1969:
1970:          default:
1971:                 // handle remaining 2xy, 4xy & 5xy codes
1972:•                if (retCode >= 400 && retCode <= 499) {
1973:                  // assume address is valid, although we don't really know
1974:                  validUnsent.add(ia);
1975:•                } else if (retCode >= 500 && retCode <= 599) {
1976:                         // assume address is invalid, although we don't really know
1977:                         invalid.add(ia);
1978:•                } else if (retCode >= 200 && retCode <= 299) {
1979:                         // see RFC 5321 section 4.3.2
1980:                         // assume address is valid, although we don't really know
1981:                         valid.add(ia);
1982:•                        if (!reportSuccess)
1983:                                 break;
1984:
1985:                         // user wants exception even when successful, including
1986:                         // details of the return code
1987:
1988:                         // create and chain the exception
1989:                         sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
1990:                                         lastServerResponse);
1991:•                        if (mex == null)
1992:                                 mex = sfex;
1993:                         else
1994:                                 mex.setNextException(sfex);
1995:                 } else {
1996:                  // completely unexpected response, just give up
1997:•                 if (logger.isLoggable(Level.FINE))
1998:                         logger.fine("got response code " + retCode +
1999:                          ", with response: " + lastServerResponse);
2000:                  String _lsr = lastServerResponse; // else rset will nuke it
2001:                  int _lrc = lastReturnCode;
2002:•                 if (serverSocket != null)        // hasn't already been closed
2003:                         issueCommand("RSET", -1);
2004:                  lastServerResponse = _lsr;        // restore, for get
2005:                  lastReturnCode = _lrc;
2006:                  throw new SMTPAddressFailedException(ia, cmd, retCode,
2007:                                                                 _lsr);
2008:                 }
2009:•                if (!sendPartial)
2010:                  sendFailed = true;
2011:                 // create and chain the exception
2012:                 sfex = new SMTPAddressFailedException(ia, cmd, retCode,
2013:                                                         lastServerResponse);
2014:•                if (mex == null)
2015:                  mex = sfex;
2016:                 else
2017:                  mex.setNextException(sfex);
2018:                 break;
2019:          }
2020:         }
2021:
2022:         // if we're willing to send to a partial list, and we found no
2023:         // valid addresses, that's complete failure
2024:•        if (sendPartial && valid.size() == 0)
2025:          sendFailed = true;
2026:
2027:         // copy the lists into appropriate arrays
2028:•        if (sendFailed) {
2029:          // copy invalid addrs
2030:          invalidAddr = new Address[invalid.size()];
2031:          invalid.toArray(invalidAddr);
2032:
2033:          // copy all valid addresses to validUnsent, since something failed
2034:          validUnsentAddr = new Address[valid.size() + validUnsent.size()];
2035:          int i = 0;
2036:•         for (int j = 0; j < valid.size(); j++)
2037:                 validUnsentAddr[i++] = (Address)valid.get(j);
2038:•         for (int j = 0; j < validUnsent.size(); j++)
2039:                 validUnsentAddr[i++] = (Address)validUnsent.get(j);
2040:•        } else if (reportSuccess || (sendPartial &&
2041:•                        (invalid.size() > 0 || validUnsent.size() > 0))) {
2042:          // we'll go on to send the message, but after sending we'll
2043:          // throw an exception with this exception nested
2044:          sendPartiallyFailed = true;
2045:          exception = mex;
2046:
2047:          // copy invalid addrs
2048:          invalidAddr = new Address[invalid.size()];
2049:          invalid.toArray(invalidAddr);
2050:
2051:          // copy valid unsent addresses to validUnsent
2052:          validUnsentAddr = new Address[validUnsent.size()];
2053:          validUnsent.toArray(validUnsentAddr);
2054:
2055:          // copy valid addresses to validSent
2056:          validSentAddr = new Address[valid.size()];
2057:          valid.toArray(validSentAddr);
2058:         } else { // all addresses pass
2059:          validSentAddr = addresses;
2060:         }
2061:
2062:
2063:         // print out the debug info
2064:•        if (logger.isLoggable(Level.FINE)) {
2065:•         if (validSentAddr != null && validSentAddr.length > 0) {
2066:                 logger.fine("Verified Addresses");
2067:•                for (int l = 0; l < validSentAddr.length; l++) {
2068:                  logger.fine(" " + validSentAddr[l]);
2069:                 }
2070:          }
2071:•         if (validUnsentAddr != null && validUnsentAddr.length > 0) {
2072:                 logger.fine("Valid Unsent Addresses");
2073:•                for (int j = 0; j < validUnsentAddr.length; j++) {
2074:                  logger.fine(" " + validUnsentAddr[j]);
2075:                 }
2076:          }
2077:•         if (invalidAddr != null && invalidAddr.length > 0) {
2078:                 logger.fine("Invalid Addresses");
2079:•                for (int k = 0; k < invalidAddr.length; k++) {
2080:                  logger.fine(" " + invalidAddr[k]);
2081:                 }
2082:          }
2083:         }
2084:
2085:         // throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
2086:•        if (sendFailed) {
2087:          logger.fine(
2088:                 "Sending failed because of invalid destination addresses");
2089:          notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
2090:                                  validSentAddr, validUnsentAddr,
2091:                                  invalidAddr, this.message);
2092:
2093:          // reset the connection so more sends are allowed
2094:          String lsr = lastServerResponse;        // save, for get
2095:          int lrc = lastReturnCode;
2096:          try {
2097:•                if (serverSocket != null)
2098:                  issueCommand("RSET", -1);
2099:          } catch (MessagingException ex) {
2100:                 // if can't reset, best to close the connection
2101:                 try {
2102:                  close();
2103:                 } catch (MessagingException ex2) {
2104:                  // thrown by close()--ignore, will close() later anyway
2105:                  logger.log(Level.FINE, "close failed", ex2);
2106:                 }
2107:          } finally {
2108:                 lastServerResponse = lsr;        // restore
2109:                 lastReturnCode = lrc;
2110:          }
2111:
2112:          throw new SendFailedException("Invalid Addresses", mex,
2113:                                          validSentAddr,
2114:                                          validUnsentAddr, invalidAddr);
2115:         }
2116: }
2117:
2118: /**
2119: * Send the <code>DATA</code> command to the SMTP host and return
2120: * an OutputStream to which the data is to be written.
2121: *
2122: * @return                the stream to write to
2123: * @exception        MessagingException for failures
2124: * @since JavaMail 1.4.1
2125: */
2126: protected OutputStream data() throws MessagingException {
2127:•        assert Thread.holdsLock(this);
2128:         issueSendCommand("DATA", 354);
2129:         dataStream = new SMTPOutputStream(serverOutput);
2130:         return dataStream;
2131: }
2132:
2133: /**
2134: * Terminate the sent data.
2135: *
2136: * @exception        IOException for I/O errors
2137: * @exception        MessagingException for other failures
2138: * @since JavaMail 1.4.1
2139: */
2140: protected void finishData() throws IOException, MessagingException {
2141:•        assert Thread.holdsLock(this);
2142:         dataStream.ensureAtBOL();
2143:         issueSendCommand(".", 250);
2144: }
2145:
2146: /**
2147: * Return a stream that will use the SMTP BDAT command to send data.
2148: *
2149: * @return                the stream to write to
2150: * @exception        MessagingException for failures
2151: * @since JavaMail 1.6.0
2152: */
2153: protected OutputStream bdat() throws MessagingException {
2154:•        assert Thread.holdsLock(this);
2155:         dataStream = new BDATOutputStream(serverOutput, chunkSize);
2156:         return dataStream;
2157: }
2158:
2159: /**
2160: * Terminate the sent data.
2161: *
2162: * @exception        IOException for I/O errors
2163: * @exception        MessagingException for other failures
2164: * @since JavaMail 1.6.0
2165: */
2166: protected void finishBdat() throws IOException, MessagingException {
2167:•        assert Thread.holdsLock(this);
2168:         dataStream.ensureAtBOL();
2169:         dataStream.close();        // doesn't close underlying socket
2170: }
2171:
2172: /**
2173: * Issue the <code>STARTTLS</code> command and switch the socket to
2174: * TLS mode if it succeeds.
2175: *
2176: * @exception        MessagingException for failures
2177: * @since JavaMail 1.4.1
2178: */
2179: protected void startTLS() throws MessagingException {
2180:         issueCommand("STARTTLS", 220);
2181:         // it worked, now switch the socket into TLS mode
2182:         try {
2183:          serverSocket = SocketFetcher.startTLS(serverSocket, host,
2184:                                 session.getProperties(), "mail." + name);
2185:          initStreams();
2186:         } catch (IOException ioex) {
2187:          closeConnection();
2188:          throw new MessagingException("Could not convert socket to TLS",
2189:                                                                 ioex);
2190:         }
2191: }
2192:
2193: /////// primitives ///////
2194:
2195: /**
2196: * Connect to host on port and start the SMTP protocol.
2197: */
2198: private void openServer(String host, int port)
2199:                                 throws MessagingException {
2200:
2201:• if (logger.isLoggable(Level.FINE))
2202:          logger.fine("trying to connect to host \"" + host +
2203:                                 "\", port " + port + ", isSSL " + isSSL);
2204:
2205:         try {
2206:          Properties props = session.getProperties();
2207:
2208:          serverSocket = SocketFetcher.getSocket(host, port,
2209:                 props, "mail." + name, isSSL);
2210:
2211:          // socket factory may've chosen a different port,
2212:          // update it for the debug messages that follow
2213:          port = serverSocket.getPort();
2214:          // save host name for startTLS
2215:          this.host = host;
2216:
2217:          initStreams();
2218:
2219:          int r = -1;
2220:•         if ((r = readServerResponse()) != 220) {
2221:                 String failResponse = lastServerResponse;
2222:                 try {
2223:•                 if (quitOnSessionReject) {
2224:                         sendCommand("QUIT");
2225:•                        if (quitWait) {
2226:                          int resp = readServerResponse();
2227:•                         if (resp != 221 && resp != -1 &&
2228:•                                 logger.isLoggable(Level.FINE))
2229:                                 logger.fine("QUIT failed with " + resp);
2230:                         }
2231:                  }
2232:                 } catch (Exception e) {
2233:•                 if (logger.isLoggable(Level.FINE))
2234:                         logger.log(Level.FINE, "QUIT failed", e);
2235:                 } finally {
2236:                  serverSocket.close();
2237:                  serverSocket = null;
2238:                  serverOutput = null;
2239:                  serverInput = null;
2240:                  lineInputStream = null;
2241:                 }
2242:•                if (logger.isLoggable(Level.FINE))
2243:                  logger.fine("got bad greeting from host \"" +
2244:                                 host + "\", port: " + port +
2245:                                 ", response: " + failResponse);
2246:                 throw new MessagingException(
2247:                                 "Got bad greeting from SMTP host: " + host +
2248:                                 ", port: " + port +
2249:                                 ", response: " + failResponse);
2250:          } else {
2251:•                if (logger.isLoggable(Level.FINE))
2252:                  logger.fine("connected to host \"" +
2253:                                  host + "\", port: " + port);
2254:          }
2255:         } catch (UnknownHostException uhex) {
2256:          throw new MessagingException("Unknown SMTP host: " + host, uhex);
2257:         } catch (SocketConnectException scex) {
2258:          throw new MailConnectException(scex);
2259:         } catch (IOException ioe) {
2260:          throw new MessagingException("Could not connect to SMTP host: " +
2261:                                  host + ", port: " + port, ioe);
2262:         }
2263: }
2264:
2265: /**
2266: * Start the protocol to the server on serverSocket,
2267: * assumed to be provided and connected by the caller.
2268: */
2269: private void openServer() throws MessagingException {
2270:         int port = -1;
2271:         host = "UNKNOWN";
2272:         try {
2273:          port = serverSocket.getPort();
2274:          host = serverSocket.getInetAddress().getHostName();
2275:•         if (logger.isLoggable(Level.FINE))
2276:                 logger.fine("starting protocol to host \"" +
2277:                                         host + "\", port " + port);
2278:
2279:          initStreams();
2280:
2281:          int r = -1;
2282:•         if ((r = readServerResponse()) != 220) {
2283: try {
2284:• if (quitOnSessionReject) {
2285: sendCommand("QUIT");
2286:• if (quitWait) {
2287: int resp = readServerResponse();
2288:• if (resp != 221 && resp != -1 &&
2289:• logger.isLoggable(Level.FINE))
2290: logger.fine("QUIT failed with " + resp);
2291: }
2292: }
2293: } catch (Exception e) {
2294:• if (logger.isLoggable(Level.FINE))
2295: logger.log(Level.FINE, "QUIT failed", e);
2296: } finally {
2297: serverSocket.close();
2298: serverSocket = null;
2299: serverOutput = null;
2300: serverInput = null;
2301: lineInputStream = null;
2302: }
2303:•                if (logger.isLoggable(Level.FINE))
2304:                  logger.fine("got bad greeting from host \"" +
2305:                                  host + "\", port: " + port +
2306:                                  ", response: " + r);
2307:                 throw new MessagingException(
2308:                         "Got bad greeting from SMTP host: " + host +
2309:                                  ", port: " + port +
2310:                                  ", response: " + r);
2311:          } else {
2312:•                if (logger.isLoggable(Level.FINE))
2313:                  logger.fine("protocol started to host \"" +
2314:                                  host + "\", port: " + port);
2315:          }
2316:         } catch (IOException ioe) {
2317:          throw new MessagingException(
2318:                                  "Could not start protocol to SMTP host: " +
2319:                                  host + ", port: " + port, ioe);
2320:         }
2321: }
2322:
2323:
2324: private void initStreams() throws IOException {
2325:         boolean quote = PropUtil.getBooleanProperty(session.getProperties(),
2326:                                         "mail.debug.quote", false);
2327:
2328:         traceInput =
2329:          new TraceInputStream(serverSocket.getInputStream(), traceLogger);
2330:         traceInput.setQuote(quote);
2331:
2332:         traceOutput =
2333:          new TraceOutputStream(serverSocket.getOutputStream(), traceLogger);
2334:         traceOutput.setQuote(quote);
2335:
2336:         serverOutput =
2337:          new BufferedOutputStream(traceOutput);
2338:         serverInput =
2339:          new BufferedInputStream(traceInput);
2340:         lineInputStream = new LineInputStream(serverInput);
2341: }
2342:
2343: /**
2344: * Is protocol tracing enabled?
2345: */
2346: private boolean isTracing() {
2347:         return traceLogger.isLoggable(Level.FINEST);
2348: }
2349:
2350: /**
2351: * Temporarily turn off protocol tracing, e.g., to prevent
2352: * tracing the authentication sequence, including the password.
2353: */
2354: private void suspendTracing() {
2355:•        if (traceLogger.isLoggable(Level.FINEST)) {
2356:          traceInput.setTrace(false);
2357:          traceOutput.setTrace(false);
2358:         }
2359: }
2360:
2361: /**
2362: * Resume protocol tracing, if it was enabled to begin with.
2363: */
2364: private void resumeTracing() {
2365:•        if (traceLogger.isLoggable(Level.FINEST)) {
2366:          traceInput.setTrace(true);
2367:          traceOutput.setTrace(true);
2368:         }
2369: }
2370:
2371: /**
2372: * Send the command to the server. If the expected response code
2373: * is not received, throw a MessagingException.
2374: *
2375: * @param        cmd        the command to send
2376: * @param        expect        the expected response code (-1 means don't care)
2377: * @exception        MessagingException for failures
2378: * @since JavaMail 1.4.1
2379: */
2380: public synchronized void issueCommand(String cmd, int expect)
2381:                                 throws MessagingException {
2382:         sendCommand(cmd);
2383:
2384:         // if server responded with an unexpected return code,
2385:         // throw the exception, notifying the client of the response
2386:         int resp = readServerResponse();
2387:•        if (expect != -1 && resp != expect)
2388:          throw new MessagingException(lastServerResponse);
2389: }
2390:
2391: /**
2392: * Issue a command that's part of sending a message.
2393: */
2394: private void issueSendCommand(String cmd, int expect)
2395:                                 throws MessagingException {
2396:         sendCommand(cmd);
2397:
2398:         // if server responded with an unexpected return code,
2399:         // throw the exception, notifying the client of the response
2400:         int ret;
2401:•        if ((ret = readServerResponse()) != expect) {
2402:          // assume message was not sent to anyone,
2403:          // combine valid sent & unsent addresses
2404:•         int vsl = validSentAddr == null ? 0 : validSentAddr.length;
2405:•         int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
2406:          Address[] valid = new Address[vsl + vul];
2407:•         if (vsl > 0)
2408:                 System.arraycopy(validSentAddr, 0, valid, 0, vsl);
2409:•         if (vul > 0)
2410:                 System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
2411:          validSentAddr = null;
2412:          validUnsentAddr = valid;
2413:•         if (logger.isLoggable(Level.FINE))
2414:                 logger.fine("got response code " + ret +
2415:                  ", with response: " + lastServerResponse);
2416:          String _lsr = lastServerResponse; // else rset will nuke it
2417:          int _lrc = lastReturnCode;
2418:•         if (serverSocket != null)        // hasn't already been closed
2419:                 issueCommand("RSET", -1);
2420:          lastServerResponse = _lsr;        // restore, for get
2421:          lastReturnCode = _lrc;
2422:          throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
2423:                         exception, validSentAddr, validUnsentAddr, invalidAddr);
2424:         }
2425: }
2426:
2427: /**
2428: * Send the command to the server and return the response code
2429: * from the server.
2430: *
2431: * @param        cmd        the command
2432: * @return                the response code
2433: * @exception        MessagingException for failures
2434: * @since JavaMail 1.4.1
2435: */
2436: public synchronized int simpleCommand(String cmd)
2437:                                 throws MessagingException {
2438:         sendCommand(cmd);
2439:         return readServerResponse();
2440: }
2441:
2442: /**
2443: * Send the command to the server and return the response code
2444: * from the server.
2445: *
2446: * @param        cmd        the command
2447: * @return                the response code
2448: * @exception        MessagingException for failures
2449: * @since JavaMail 1.4.1
2450: */
2451: protected int simpleCommand(byte[] cmd) throws MessagingException {
2452:•        assert Thread.holdsLock(this);
2453:         sendCommand(cmd);
2454:         return readServerResponse();
2455: }
2456:
2457: /**
2458: * Sends command <code>cmd</code> to the server terminating
2459: * it with <code>CRLF</code>.
2460: *
2461: * @param        cmd        the command
2462: * @exception        MessagingException for failures
2463: * @since JavaMail 1.4.1
2464: */
2465: protected void sendCommand(String cmd) throws MessagingException {
2466:         sendCommand(toBytes(cmd));
2467: }
2468:
2469: private void sendCommand(byte[] cmdBytes) throws MessagingException {
2470:•        assert Thread.holdsLock(this);
2471:         //if (logger.isLoggable(Level.FINE))
2472:          //logger.fine("SENT: " + new String(cmdBytes, 0));
2473:
2474: try {
2475:          serverOutput.write(cmdBytes);
2476:          serverOutput.write(CRLF);
2477:          serverOutput.flush();
2478:         } catch (IOException ex) {
2479:          throw new MessagingException("Can't send command to SMTP host", ex);
2480:         }
2481: }
2482:
2483: /**
2484: * Reads server reponse returning the <code>returnCode</code>
2485: * as the number. Returns -1 on failure. Sets
2486: * <code>lastServerResponse</code> and <code>lastReturnCode</code>.
2487: *
2488: * @return                server response code
2489: * @exception        MessagingException for failures
2490: * @since JavaMail 1.4.1
2491: */
2492: protected int readServerResponse() throws MessagingException {
2493:•        assert Thread.holdsLock(this);
2494: String serverResponse = "";
2495: int returnCode = 0;
2496:         StringBuilder buf = new StringBuilder(100);
2497:
2498:         // read the server response line(s) and add them to the buffer
2499:         // that stores the response
2500: try {
2501:          String line = null;
2502:
2503:          do {
2504:                 line = lineInputStream.readLine();
2505:•                if (line == null) {
2506:                  serverResponse = buf.toString();
2507:•                 if (serverResponse.length() == 0)
2508:                         serverResponse = "[EOF]";
2509:                  lastServerResponse = serverResponse;
2510:                  lastReturnCode = -1;
2511:                  logger.log(Level.FINE, "EOF: {0}", serverResponse);
2512:                  return -1;
2513:                 }
2514:                 buf.append(line);
2515:                 buf.append("\n");
2516:•         } while (isNotLastLine(line));
2517:
2518: serverResponse = buf.toString();
2519: } catch (IOException ioex) {
2520:          logger.log(Level.FINE, "exception reading response", ioex);
2521: //ioex.printStackTrace(out);
2522:          lastServerResponse = "";
2523:          lastReturnCode = 0;
2524:          throw new MessagingException("Exception reading response", ioex);
2525: //returnCode = -1;
2526: }
2527:
2528:         // print debug info
2529: //if (logger.isLoggable(Level.FINE))
2530: //logger.fine("RCVD: " + serverResponse);
2531:
2532:         // parse out the return code
2533:• if (serverResponse.length() >= 3) {
2534: try {
2535: returnCode = Integer.parseInt(serverResponse.substring(0, 3));
2536: } catch (NumberFormatException nfe) {
2537:                 try {
2538:                  close();
2539:                 } catch (MessagingException mex) {
2540:                  // thrown by close()--ignore, will close() later anyway
2541:                  logger.log(Level.FINE, "close failed", mex);
2542:                 }
2543:                 returnCode = -1;
2544: } catch (StringIndexOutOfBoundsException ex) {
2545:                 try {
2546:                  close();
2547:                 } catch (MessagingException mex) {
2548:                  // thrown by close()--ignore, will close() later anyway
2549:                  logger.log(Level.FINE, "close failed", mex);
2550:                 }
2551: returnCode = -1;
2552:          }
2553:         } else {
2554:          returnCode = -1;
2555:         }
2556:•        if (returnCode == -1)
2557:          logger.log(Level.FINE, "bad server response: {0}", serverResponse);
2558:
2559: lastServerResponse = serverResponse;
2560:         lastReturnCode = returnCode;
2561: return returnCode;
2562: }
2563:
2564: /**
2565: * Check if we're in the connected state. Don't bother checking
2566: * whether the server is still alive, that will be detected later.
2567: *
2568: * @exception        IllegalStateException        if not connected
2569: *
2570: * @since JavaMail 1.4.1
2571: */
2572: protected void checkConnected() {
2573:•        if (!super.isConnected())
2574:          throw new IllegalStateException("Not connected");
2575: }
2576:
2577: // tests if the <code>line</code> is an intermediate line according to SMTP
2578: private boolean isNotLastLine(String line) {
2579:• return line != null && line.length() >= 4 && line.charAt(3) == '-';
2580: }
2581:
2582: // wraps an address in "<>"'s if necessary
2583: private String normalizeAddress(String addr) {
2584:•        if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
2585:          return "<" + addr + ">";
2586:         else
2587:          return addr;
2588: }
2589:
2590: /**
2591: * Return true if the SMTP server supports the specified service
2592: * extension. Extensions are reported as results of the EHLO
2593: * command when connecting to the server. See
2594: * <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
2595: * and other RFCs that define specific extensions.
2596: *
2597: * @param        ext        the service extension name
2598: * @return                true if the extension is supported
2599: *
2600: * @since JavaMail 1.3.2
2601: */
2602: public boolean supportsExtension(String ext) {
2603:•        return extMap != null &&
2604:•                        extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
2605: }
2606:
2607: /**
2608: * Return the parameter the server provided for the specified
2609: * service extension, or null if the extension isn't supported.
2610: *
2611: * @param        ext        the service extension name
2612: * @return                the extension parameter
2613: *
2614: * @since JavaMail 1.3.2
2615: */
2616: public String getExtensionParameter(String ext) {
2617:•        return extMap == null ? null :
2618:                         extMap.get(ext.toUpperCase(Locale.ENGLISH));
2619: }
2620:
2621: /**
2622: * Does the server we're connected to support the specified
2623: * authentication mechanism? Uses the extension information
2624: * returned by the server from the EHLO command.
2625: *
2626: * @param        auth        the authentication mechanism
2627: * @return                true if the authentication mechanism is supported
2628: *
2629: * @since JavaMail 1.4.1
2630: */
2631: protected boolean supportsAuthentication(String auth) {
2632:•        assert Thread.holdsLock(this);
2633:•        if (extMap == null)
2634:          return false;
2635:         String a = extMap.get("AUTH");
2636:•        if (a == null)
2637:          return false;
2638:         StringTokenizer st = new StringTokenizer(a);
2639:•        while (st.hasMoreTokens()) {
2640:          String tok = st.nextToken();
2641:•         if (tok.equalsIgnoreCase(auth))
2642:                 return true;
2643:         }
2644:         // hack for buggy servers that advertise capability incorrectly
2645:•        if (auth.equalsIgnoreCase("LOGIN") && supportsExtension("AUTH=LOGIN")) {
2646:          logger.fine("use AUTH=LOGIN hack");
2647:          return true;
2648:         }
2649:         return false;
2650: }
2651:
2652: private static char[] hexchar = {
2653:         '0', '1', '2', '3', '4', '5', '6', '7',
2654:         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
2655: };
2656:
2657: /**
2658: * Convert a string to RFC 1891 xtext format.
2659: *
2660: * <pre>
2661: * xtext = *( xchar / hexchar )
2662: *
2663: * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
2664: * except for "+" and "=".
2665: *
2666: * ; "hexchar"s are intended to encode octets that cannot appear
2667: * ; as ASCII characters within an esmtp-value.
2668: *
2669: * hexchar = ASCII "+" immediately followed by two upper case
2670: * hexadecimal digits
2671: * </pre>
2672: *
2673: * @param        s        the string to convert
2674: * @return        the xtext format string
2675: * @since JavaMail 1.4.1
2676: */
2677: // XXX - keeping this around only for compatibility
2678: protected static String xtext(String s) {
2679:         return xtext(s, false);
2680: }
2681:
2682: /**
2683: * Like xtext(s), but allow UTF-8 strings.
2684: *
2685: * @param        s        the string to convert
2686: * @param        utf8        convert string to UTF-8 first?
2687: * @return        the xtext format string
2688: * @since JavaMail 1.6.0
2689: */
2690: protected static String xtext(String s, boolean utf8) {
2691:         StringBuilder sb = null;
2692:         byte[] bytes;
2693:•        if (utf8)
2694:          bytes = s.getBytes(StandardCharsets.UTF_8);
2695:         else
2696:          bytes = ASCIIUtility.getBytes(s);
2697:•        for (int i = 0; i < bytes.length; i++) {
2698:          char c = (char)(((int)bytes[i])&0xff);
2699:•         if (!utf8 && c >= 128)        // not ASCII
2700:                 throw new IllegalArgumentException(
2701:                          "Non-ASCII character in SMTP submitter: " + s);
2702:•         if (c < '!' || c > '~' || c == '+' || c == '=') {
2703:                 // not printable ASCII
2704:•                if (sb == null) {
2705:                  sb = new StringBuilder(s.length() + 4);
2706:                  sb.append(s.substring(0, i));
2707:                 }
2708:                 sb.append('+');
2709:                 sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
2710:                 sb.append(hexchar[((int)c)& 0x0f]);
2711:          } else {
2712:•                if (sb != null)
2713:                  sb.append(c);
2714:          }
2715:         }
2716:•        return sb != null ? sb.toString() : s;
2717: }
2718:
2719: private String traceUser(String user) {
2720:•        return debugusername ? user : "<user name suppressed>";
2721: }
2722:
2723: private String tracePassword(String password) {
2724:•        return debugpassword ? password :
2725:•                                (password == null ? "<null>" : "<non-null>");
2726: }
2727:
2728: /**
2729: * Convert the String to either ASCII or UTF-8 bytes
2730: * depending on allowutf8.
2731: */
2732: private byte[] toBytes(String s) {
2733:•        if (allowutf8)
2734:          return s.getBytes(StandardCharsets.UTF_8);
2735:         else
2736:          // don't use StandardCharsets.US_ASCII because it rejects non-ASCII
2737:          return ASCIIUtility.getBytes(s);
2738: }
2739:
2740: /*
2741: * Probe points for GlassFish monitoring.
2742: */
2743: private void sendMessageStart(String subject) { }
2744: private void sendMessageEnd() { }
2745:
2746:
2747: /**
2748: * An SMTPOutputStream that wraps a ChunkedOutputStream.
2749: */
2750: private class BDATOutputStream extends SMTPOutputStream {
2751:
2752:         /**
2753:          * Create a BDATOutputStream that wraps a ChunkedOutputStream
2754:          * of the given size and built on top of the specified
2755:          * underlying output stream.
2756:          *
2757:          * @param        out        the underlying output stream
2758:          * @param        size        the chunk size
2759:          */
2760:         public BDATOutputStream(OutputStream out, int size) {
2761:          super(new ChunkedOutputStream(out, size));
2762:         }
2763:
2764:         /**
2765:          * Close this output stream.
2766:          *
2767:          * @exception        IOException        for I/O errors
2768:          */
2769:         @Override
2770:         public void close() throws IOException {
2771:          out.close();
2772:         }
2773: }
2774:
2775: /**
2776: * An OutputStream that buffers data in chunks and uses the
2777: * RFC 3030 BDAT SMTP command to send each chunk.
2778: */
2779: private class ChunkedOutputStream extends OutputStream {
2780:         private final OutputStream out;
2781:         private final byte[] buf;
2782:         private int count = 0;
2783:
2784:         /**
2785:          * Create a ChunkedOutputStream built on top of the specified
2786:          * underlying output stream.
2787:          *
2788:          * @param        out        the underlying output stream
2789:          * @param        size        the chunk size
2790:          */
2791:         public ChunkedOutputStream(OutputStream out, int size) {
2792:          this.out = out;
2793:          buf = new byte[size];
2794:         }
2795:
2796:         /**
2797:          * Writes the specified <code>byte</code> to this output stream.
2798:          *
2799:          * @param        b        the byte to write
2800:          * @exception        IOException        for I/O errors
2801:          */
2802:         @Override
2803:         public void write(int b) throws IOException {
2804:          buf[count++] = (byte)b;
2805:          if (count >= buf.length)
2806:                 flush();
2807:         }
2808:
2809:         /**
2810:          * Writes len bytes to this output stream starting at off.
2811:          *
2812:          * @param        b        bytes to write
2813:          * @param        off        offset in array
2814:          * @param        len        number of bytes to write
2815:          * @exception        IOException        for I/O errors
2816:          */
2817:         @Override
2818:         public void write(byte b[], int off, int len) throws IOException {
2819:          while (len > 0) {
2820:                 int size = Math.min(buf.length - count, len);
2821:                 if (size == buf.length) {
2822:                  // avoid the copy
2823:                  bdat(b, off, size, false);
2824:                 } else {
2825:                  System.arraycopy(b, off, buf, count, size);
2826:                  count += size;
2827:                 }
2828:                 off += size;
2829:                 len -= size;
2830:                 if (count >= buf.length)
2831:                  flush();
2832:          }
2833:         }
2834:
2835:         /**
2836:          * Flush this output stream.
2837:          *
2838:          * @exception        IOException        for I/O errors
2839:          */
2840:         @Override
2841:         public void flush() throws IOException {
2842:          bdat(buf, 0, count, false);
2843:          count = 0;
2844:         }
2845:
2846:         /**
2847:          * Close this output stream.
2848:          *
2849:          * @exception        IOException        for I/O errors
2850:          */
2851:         @Override
2852:         public void close() throws IOException {
2853:          bdat(buf, 0, count, true);
2854:          count = 0;
2855:         }
2856:
2857:         /**
2858:          * Send the specified bytes using the BDAT command.
2859:          */
2860:         private void bdat(byte[] b, int off, int len, boolean last)
2861:                                 throws IOException {
2862:          if (len > 0 || last) {
2863:                 try {
2864:                  if (last)
2865:                         sendCommand("BDAT " + len + " LAST");
2866:                  else
2867:                         sendCommand("BDAT " + len);
2868:                  out.write(b, off, len);
2869:                  out.flush();
2870:                  int ret = readServerResponse();
2871:                  if (ret != 250)
2872:                         throw new IOException(lastServerResponse);
2873:                 } catch (MessagingException mex) {
2874:                  throw new IOException("BDAT write exception", mex);
2875:                 }
2876:          }
2877:         }
2878: }
2879: }