1 |
/* |
2 |
GNU-Classpath Extensions: GNU Javamail - SMTP Service Provider |
3 |
Copyright (C) 2001 Benjamin A. Speakmon |
4 |
|
5 |
For more information on the classpathx please mail: classpathx-discuss@gnu.org |
6 |
|
7 |
This program is free software; you can redistribute it and/or |
8 |
modify it under the terms of the GNU Lesser General Public License |
9 |
as published by the Free Software Foundation; either version 2 |
10 |
of the License, or (at your option) any later version. |
11 |
|
12 |
This program is distributed in the hope that it will be useful, |
13 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
GNU General Public License for more details. |
16 |
|
17 |
You should have received a copy of the GNU Lesser General Public License |
18 |
along with this program; if not, write to the Free Software |
19 |
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
20 |
*/ |
21 |
|
22 |
package gnu.mail.providers.smtp; |
23 |
|
24 |
import java.io.IOException; |
25 |
import java.io.OutputStream; |
26 |
import java.net.InetAddress; |
27 |
import java.net.UnknownHostException; |
28 |
import java.util.ArrayList; |
29 |
import java.util.Iterator; |
30 |
import java.util.List; |
31 |
import java.util.StringTokenizer; |
32 |
import javax.mail.Address; |
33 |
import javax.mail.Message; |
34 |
import javax.mail.MessagingException; |
35 |
import javax.mail.SendFailedException; |
36 |
import javax.mail.Session; |
37 |
import javax.mail.Transport; |
38 |
import javax.mail.URLName; |
39 |
import javax.mail.event.TransportEvent; |
40 |
import javax.mail.internet.InternetAddress; |
41 |
import javax.mail.internet.MimeMessage; |
42 |
|
43 |
import gnu.inet.smtp.Parameter; |
44 |
import gnu.inet.smtp.ParameterList; |
45 |
import gnu.inet.smtp.SMTPConnection; |
46 |
import gnu.inet.util.Logger; |
47 |
|
48 |
/** |
49 |
* This transport handles communications with an SMTP server. |
50 |
* |
51 |
* @author Andrew Selkirk |
52 |
* @author Ben Speakmon |
53 |
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
54 |
* @version 2.0 |
55 |
*/ |
56 |
public class SMTPTransport |
57 |
extends Transport |
58 |
{ |
59 |
|
60 |
/** |
61 |
* The connection used to communicate with the server. |
62 |
*/ |
63 |
protected SMTPConnection connection; |
64 |
|
65 |
protected String localHostName; |
66 |
|
67 |
private List extensions = null; |
68 |
private List authenticationMechanisms = null; |
69 |
|
70 |
/** |
71 |
* Creates a new <code>SMTPTransport</code> instance. |
72 |
* |
73 |
* @param session a <code>Session</code> value |
74 |
* @param urlName an <code>URLName</code> value |
75 |
*/ |
76 |
public SMTPTransport(Session session, URLName urlName) |
77 |
{ |
78 |
super(session, urlName); |
79 |
|
80 |
// Check for mail.smtp.localhost property |
81 |
localHostName = getProperty("localhost"); |
82 |
if (localHostName==null) |
83 |
{ |
84 |
try |
85 |
{ |
86 |
localHostName = InetAddress.getLocalHost().getHostName(); |
87 |
} |
88 |
catch (UnknownHostException e) |
89 |
{ |
90 |
localHostName = "localhost"; |
91 |
} |
92 |
} |
93 |
} |
94 |
|
95 |
/** |
96 |
* Connects to the SMTP server. |
97 |
*/ |
98 |
protected boolean protocolConnect(String host, int port, String username, |
99 |
String password) |
100 |
throws MessagingException |
101 |
{ |
102 |
if (connection!=null) |
103 |
return true; |
104 |
if (host==null) |
105 |
host = getProperty("host"); |
106 |
if (port<0) |
107 |
port = getIntProperty("port"); |
108 |
if (username==null) |
109 |
username = getProperty("user"); |
110 |
|
111 |
// Check host |
112 |
if (host==null) |
113 |
throw new MessagingException("No SMTP host set (mail.smtp.host)"); |
114 |
|
115 |
try |
116 |
{ |
117 |
int connectionTimeout = getIntProperty("connectiontimeout"); |
118 |
int timeout = getIntProperty("timeout"); |
119 |
connection = new SMTPConnection(host, port, |
120 |
connectionTimeout, timeout, session.getDebug()); |
121 |
|
122 |
// EHLO/HELO |
123 |
if (propertyIsFalse("ehlo")) |
124 |
{ |
125 |
if (!connection.helo(localHostName)) |
126 |
throw new MessagingException("HELO failed: "+ |
127 |
connection.getLastResponse()); |
128 |
} |
129 |
else |
130 |
{ |
131 |
extensions = connection.ehlo(localHostName); |
132 |
if (extensions==null) |
133 |
{ |
134 |
if (!connection.helo(localHostName)) |
135 |
throw new MessagingException("HELO failed: "+ |
136 |
connection.getLastResponse()); |
137 |
} |
138 |
else |
139 |
{ |
140 |
for (Iterator i=extensions.iterator(); i.hasNext(); ) |
141 |
{ |
142 |
String extension = (String)i.next(); |
143 |
if (extension.startsWith("AUTH ")) |
144 |
{ |
145 |
// Populate authenticationMechanisms |
146 |
authenticationMechanisms = new ArrayList(); |
147 |
StringTokenizer st = new StringTokenizer(extension.substring(5)); |
148 |
while (st.hasMoreTokens()) |
149 |
authenticationMechanisms.add(st.nextToken()); |
150 |
} |
151 |
else if ("STARTTLS".equals(extension)) |
152 |
{ |
153 |
if (!propertyIsFalse("tls")) |
154 |
{ |
155 |
connection.starttls(); |
156 |
} |
157 |
} |
158 |
} |
159 |
} |
160 |
} |
161 |
|
162 |
// User authentication |
163 |
if (authenticationMechanisms!=null && |
164 |
!authenticationMechanisms.isEmpty() && |
165 |
propertyIsTrue("auth")) |
166 |
{ |
167 |
if (username!=null && password!=null) |
168 |
{ |
169 |
// TODO user ordering preferences for auth mechanisms |
170 |
for (Iterator i = authenticationMechanisms.iterator(); i.hasNext(); ) |
171 |
{ |
172 |
String mechanism = (String)i.next(); |
173 |
if (connection.authenticate(mechanism, username, password)) |
174 |
return true; |
175 |
} |
176 |
} |
177 |
return false; |
178 |
} |
179 |
return true; |
180 |
} |
181 |
catch (IOException e) |
182 |
{ |
183 |
throw new MessagingException(e.getMessage(), e); |
184 |
} |
185 |
} |
186 |
|
187 |
/** |
188 |
* Send the specified message to the server. |
189 |
*/ |
190 |
public void sendMessage(Message message, Address[] addresses) |
191 |
throws MessagingException, SendFailedException |
192 |
{ |
193 |
if (!(message instanceof MimeMessage)) |
194 |
throw new SendFailedException("only MimeMessages are supported"); |
195 |
// Cast message |
196 |
MimeMessage mimeMessage = (MimeMessage)message; |
197 |
|
198 |
int len = addresses.length; |
199 |
List sent = new ArrayList(len); |
200 |
List unsent = new ArrayList(len); |
201 |
List invalid = new ArrayList(len); |
202 |
int deliveryStatus = 0; |
203 |
ParameterList params = null; // ESMTP parameters |
204 |
|
205 |
synchronized (connection) |
206 |
{ |
207 |
if (!isConnected()) |
208 |
throw new MessagingException("not connected"); |
209 |
|
210 |
try |
211 |
{ |
212 |
// reverse-path |
213 |
String from0 = getProperty("from"); |
214 |
InternetAddress from = null; |
215 |
if (from0!=null) |
216 |
{ |
217 |
InternetAddress[] from1 = InternetAddress.parse(from0); |
218 |
if (from1!=null && from1.length>0) |
219 |
from = from1[0]; |
220 |
} |
221 |
if (from==null) |
222 |
{ |
223 |
Address[] from2 = mimeMessage.getFrom(); |
224 |
if (from2!=null && from2.length>0 && |
225 |
from2[0] instanceof InternetAddress) |
226 |
from = (InternetAddress)from2[0]; |
227 |
} |
228 |
if (from==null) |
229 |
from = InternetAddress.getLocalAddress(session); |
230 |
String reversePath = from.getAddress(); |
231 |
// DSN RET |
232 |
String dsnRet = getProperty("dsn.ret"); |
233 |
if (dsnRet!=null) |
234 |
{ |
235 |
String FULL = "FULL", HDRS = "HDRS"; |
236 |
String value = null; |
237 |
if (FULL.equalsIgnoreCase(dsnRet)) |
238 |
value = FULL; |
239 |
else if (HDRS.equalsIgnoreCase(dsnRet)) |
240 |
value = HDRS; |
241 |
if (value!=null) |
242 |
{ |
243 |
params = new ParameterList(); |
244 |
params.add(new Parameter("RET", value)); |
245 |
} |
246 |
} |
247 |
// MAIL FROM |
248 |
if (!connection.mailFrom(reversePath, params)) |
249 |
throw new SendFailedException(connection.getLastResponse()); |
250 |
params = null; |
251 |
|
252 |
// DSN NOTIFY |
253 |
String dsnNotify = getProperty("dsn.notify"); |
254 |
if (dsnNotify!=null) |
255 |
{ |
256 |
String NEVER = "NEVER", SUCCESS = "SUCCESS"; |
257 |
String FAILURE = "FAILURE", DELAY = "DELAY"; |
258 |
String value = null; |
259 |
if (NEVER.equalsIgnoreCase(dsnNotify)) |
260 |
value = NEVER; |
261 |
else |
262 |
{ |
263 |
StringBuffer buf = new StringBuffer(); |
264 |
StringTokenizer st = new StringTokenizer(dsnNotify, " ,"); |
265 |
while (st.hasMoreTokens()) |
266 |
{ |
267 |
String token = st.nextToken(); |
268 |
if (SUCCESS.equalsIgnoreCase(token)) |
269 |
{ |
270 |
if (buf.length()>0) |
271 |
buf.append(','); |
272 |
buf.append(SUCCESS); |
273 |
} |
274 |
else if (FAILURE.equalsIgnoreCase(token)) |
275 |
{ |
276 |
if (buf.length()>0) |
277 |
buf.append(','); |
278 |
buf.append(FAILURE); |
279 |
} |
280 |
else if (DELAY.equalsIgnoreCase(token)) |
281 |
{ |
282 |
if (buf.length()>0) |
283 |
buf.append(','); |
284 |
buf.append(DELAY); |
285 |
} |
286 |
} |
287 |
if (buf.length()>0) |
288 |
value = buf.toString(); |
289 |
} |
290 |
if (value!=null) |
291 |
{ |
292 |
params = new ParameterList(); |
293 |
params.add(new Parameter("NOTIFY", value)); |
294 |
} |
295 |
} |
296 |
// RCPT TO |
297 |
for (int i=0; i<addresses.length; i++) |
298 |
{ |
299 |
Address address = addresses[i]; |
300 |
if (address instanceof InternetAddress) |
301 |
{ |
302 |
String forwardPath = ((InternetAddress)address).getAddress(); |
303 |
if (connection.rcptTo(forwardPath, params)) |
304 |
sent.add(address); |
305 |
else |
306 |
invalid.add(address); |
307 |
} |
308 |
else |
309 |
invalid.add(address); |
310 |
} |
311 |
|
312 |
// DATA |
313 |
OutputStream dataStream = connection.data(); |
314 |
if (dataStream==null) |
315 |
throw new MessagingException(connection.getLastResponse()); |
316 |
mimeMessage.writeTo(dataStream); |
317 |
dataStream.flush(); |
318 |
if (connection.finishData()) |
319 |
{ |
320 |
unsent.addAll(sent); |
321 |
sent.clear(); |
322 |
deliveryStatus = TransportEvent.MESSAGE_NOT_DELIVERED; |
323 |
} |
324 |
else |
325 |
{ |
326 |
deliveryStatus = invalid.isEmpty() ? |
327 |
TransportEvent.MESSAGE_DELIVERED : |
328 |
TransportEvent.MESSAGE_PARTIALLY_DELIVERED; |
329 |
} |
330 |
} |
331 |
catch (IOException e) |
332 |
{ |
333 |
throw new SendFailedException(e.getMessage()); |
334 |
} |
335 |
} |
336 |
|
337 |
// Notify transport listeners |
338 |
Address[] a_sent = new Address[sent.size()]; |
339 |
sent.toArray(a_sent); |
340 |
Address[] a_unsent = new Address[unsent.size()]; |
341 |
unsent.toArray(a_unsent); |
342 |
Address[] a_invalid = new Address[invalid.size()]; |
343 |
invalid.toArray(a_invalid); |
344 |
|
345 |
notifyTransportListeners(deliveryStatus, a_sent, a_unsent, a_invalid, |
346 |
mimeMessage); |
347 |
} |
348 |
|
349 |
/** |
350 |
* Close this transport. |
351 |
*/ |
352 |
public void close() |
353 |
throws MessagingException |
354 |
{ |
355 |
if (isConnected()) |
356 |
{ |
357 |
synchronized (connection) |
358 |
{ |
359 |
try |
360 |
{ |
361 |
connection.quit(); |
362 |
} |
363 |
catch (IOException e) |
364 |
{ |
365 |
throw new MessagingException(e.getMessage(), e); |
366 |
} |
367 |
finally |
368 |
{ |
369 |
connection = null; |
370 |
} |
371 |
} |
372 |
} |
373 |
super.close(); |
374 |
} |
375 |
|
376 |
// -- Utility methods -- |
377 |
|
378 |
private int getIntProperty(String key) |
379 |
{ |
380 |
String value = getProperty(key); |
381 |
if (value!=null) |
382 |
{ |
383 |
try |
384 |
{ |
385 |
return Integer.parseInt(value); |
386 |
} |
387 |
catch (Exception e) |
388 |
{ |
389 |
} |
390 |
} |
391 |
return -1; |
392 |
} |
393 |
|
394 |
private boolean propertyIsFalse(String key) |
395 |
{ |
396 |
return "false".equals(getProperty(key)); |
397 |
} |
398 |
|
399 |
private boolean propertyIsTrue(String key) |
400 |
{ |
401 |
return "true".equals(getProperty(key)); |
402 |
} |
403 |
|
404 |
/* |
405 |
* Returns the provider-specific or general mail property corresponding to |
406 |
* the specified key. |
407 |
*/ |
408 |
private String getProperty(String key) |
409 |
{ |
410 |
String value = session.getProperty("mail.smtp."+key); |
411 |
if (value==null) |
412 |
value = session.getProperty("mail."+key); |
413 |
return value; |
414 |
} |
415 |
|
416 |
} |