Skip to content

Package: SMTPTransport

SMTPTransport

nameinstructionbranchcomplexitylinemethod
SMTPTransport(Session, URLName)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
SMTPTransport(Session, URLName, String, boolean)
M: 403 C: 0
0%
M: 20 C: 0
0%
M: 11 C: 0
0%
M: 58 C: 0
0%
M: 1 C: 0
0%
addressesFailed()
M: 50 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
authenticate(String, String)
M: 192 C: 0
0%
M: 24 C: 0
0%
M: 13 C: 0
0%
M: 39 C: 0
0%
M: 1 C: 0
0%
bdat()
M: 21 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
checkConnected()
M: 9 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
close()
M: 41 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
closeConnection()
M: 31 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
connect(Socket)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
convertTo8Bit(MimePart)
M: 81 C: 0
0%
M: 18 C: 0
0%
M: 10 C: 0
0%
M: 25 C: 0
0%
M: 1 C: 0
0%
data()
M: 22 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
ehlo(String)
M: 117 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 28 C: 0
0%
M: 1 C: 0
0%
expandGroups()
M: 92 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 23 C: 0
0%
M: 1 C: 0
0%
finalize()
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
finishBdat()
M: 15 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
finishData()
M: 16 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getAuthorizationId()
M: 23 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getExtensionParameter(String)
M: 13 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getLastReturnCode()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getLastServerResponse()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getLocalHost()
M: 118 C: 0
0%
M: 24 C: 0
0%
M: 13 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%
getNTLMDomain()
M: 23 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getNoopStrict()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getReportSuccess()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getRequireStartTLS()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getSASLEnabled()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getSASLMechanisms()
M: 85 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 17 C: 0
0%
M: 1 C: 0
0%
getSASLRealm()
M: 42 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
getStartTLS()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getUseCanonicalHostName()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getUseRset()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
helo(String)
M: 19 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
initStreams()
M: 57 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
is8Bit(InputStream)
M: 50 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 19 C: 0
0%
M: 1 C: 0
0%
isConnected()
M: 45 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 17 C: 0
0%
M: 1 C: 0
0%
isNotLastLine(String)
M: 15 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isSSL()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isTracing()
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
issueCommand(String, int)
M: 19 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
issueSendCommand(String, int)
M: 113 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 21 C: 0
0%
M: 1 C: 0
0%
mailFrom()
M: 294 C: 0
0%
M: 54 C: 0
0%
M: 28 C: 0
0%
M: 56 C: 0
0%
M: 1 C: 0
0%
normalizeAddress(String)
M: 21 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
notifyTransportListeners(int, Address[], Address[], Address[], Message)
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
openServer()
M: 195 C: 0
0%
M: 20 C: 0
0%
M: 11 C: 0
0%
M: 33 C: 0
0%
M: 1 C: 0
0%
openServer(String, int)
M: 231 C: 0
0%
M: 20 C: 0
0%
M: 11 C: 0
0%
M: 38 C: 0
0%
M: 1 C: 0
0%
protocolConnect(String, int, String, String)
M: 266 C: 0
0%
M: 52 C: 0
0%
M: 27 C: 0
0%
M: 53 C: 0
0%
M: 1 C: 0
0%
rcptTo()
M: 642 C: 0
0%
M: 100 C: 0
0%
M: 52 C: 0
0%
M: 135 C: 0
0%
M: 1 C: 0
0%
readServerResponse()
M: 126 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 39 C: 0
0%
M: 1 C: 0
0%
resumeTracing()
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
sasllogin(String[], String, String, String, String)
M: 176 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 30 C: 0
0%
M: 1 C: 0
0%
sendCommand(String)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
sendCommand(byte[])
M: 28 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
sendMessage(Message, Address[])
M: 300 C: 0
0%
M: 32 C: 0
0%
M: 17 C: 0
0%
M: 63 C: 0
0%
M: 1 C: 0
0%
sendMessageEnd()
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
sendMessageStart(String)
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
setAuthorizationID(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setLocalHost(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setNTLMDomain(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setNoopStrict(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setReportSuccess(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setRequireStartTLS(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setSASLEnabled(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setSASLMechanisms(String[])
M: 10 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
setSASLRealm(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setStartTLS(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setUseCanonicalHostName(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
setUseRset(boolean)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
simpleCommand(String)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
simpleCommand(byte[])
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
startTLS()
M: 36 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 93 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
supportsAuthentication(String)
M: 57 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 16 C: 0
0%
M: 1 C: 0
0%
supportsExtension(String)
M: 14 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
suspendTracing()
M: 14 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
toBytes(String)
M: 10 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
tracePassword(String)
M: 11 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
traceUser(String)
M: 7 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
xtext(String)
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
xtext(String, boolean)
M: 109 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 18 C: 0
0%
M: 1 C: 0
0%

Coverage

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