KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gnu > mail > providers > smtp > SMTPTransport


1 /*
2   GNU-Classpath Extensions: GNU Javamail - SMTP Service Provider
3   Copyright (C) 2001 Benjamin A. Speakmon
4   For more information on the classpathx please mail: classpathx-discuss@gnu.org
5   This program is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public License
7   as published by the Free Software Foundation; either version 2
8   of the License, or (at your option) any later version.
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12   GNU General Public License for more details.
13   You should have received a copy of the GNU Lesser General Public License
14   along with this program; if not, write to the Free Software
15   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 */

17 package gnu.mail.providers.smtp;
18 import java.io.*;
19
20 import java.net.*;
21 import java.util.Hashtable JavaDoc;
22 import java.util.Vector JavaDoc;
23 import javax.mail.*;
24 import javax.mail.event.TransportEvent JavaDoc;
25 import javax.mail.internet.*;
26 import gnu.mail.util.*;
27
28 /**
29  * This transport handles communications with an SMTP server.
30  *
31  * <P><B>Important System Properties</B><BR>
32  *
33  * <UL>
34  * <LI><code>mail.smtp.host</code> - the SMTP server to connect
35  * with.
36  * <LI><code>mail.smtp.user</code> - the user name to connect as
37  * (not yet implemented)
38  * <LI><code>mail.smtp.port</code> - the port used to connect
39  * to the SMTP server.
40  * <LI><code>mail.smtp.timeout</code> - milliseconds
41  * to wait for a reply from the server before timing out.
42  * <LI><code>mail.smtp.ehlo</code> - if true, will attempt
43  * to connect to SMTP server with "EHLO" first.
44  * <LI><code>mail.smtp.from</code> - the RFC 2822 address to use
45  * as the message sender
46  * <LI><code>mail.smtp.localhost</code> - the hostname to use
47  * when identifying the local host to the SMTP server
48  * </UL>
49  *
50  * @author Andrew Selkirk
51  * @author Ben Speakmon
52  * @version 2.0
53  */

54 public class SMTPTransport
55      extends Transport
56 {
57
58     //-------------------------------------------------------------
59
// Variables --------------------------------------------------
60
//-------------------------------------------------------------
61

62     //private static final String[] ignoreList = null;
63
private final static String JavaDoc CRLF = "\r\n";
64     private static int deliveryStatus;
65     private static String JavaDoc localHostName;
66
67     private MimeMessage mimeMessage;
68     private Address[] addresses;
69     //private InternetAddress[] validSentAddr;
70
private Vector JavaDoc validSentAddrList;
71     //private InternetAddress[] validUnsentAddr;
72
private Vector JavaDoc validUnsentAddrList;
73     //private InternetAddress[] invalidAddr;
74
private Vector JavaDoc invalidAddrList;
75     private Hashtable JavaDoc extMap;
76     private boolean noAuth;
77     private String JavaDoc name;
78     private BufferedReader serverInput;
79     private CRLFOutputStream serverOutput;
80     private String JavaDoc lastServerResponse;
81     private Socket socket;
82
83
84     //-------------------------------------------------------------
85
// Initialization ---------------------------------------------
86
//-------------------------------------------------------------
87

88     /**
89      * Creates a new <code>SMTPTransport</code> instance.
90      *
91      * @param session a <code>Session</code> value
92      * @param urlName an <code>URLName</code> value
93      */

94     public SMTPTransport(Session session, URLName urlName)
95     {
96         super(session, urlName);
97         validSentAddrList = new Vector JavaDoc(2);
98         validUnsentAddrList = new Vector JavaDoc(2);
99         invalidAddrList = new Vector JavaDoc();
100         // TODO
101
}
102
103
104     /**
105      * Sends the SMTP message to the server.
106      *
107      * @param message the Message to send
108      * @param addresses the Addresses to send the Message to
109      * @exception MessagingException if an error occurs
110      * @exception SendFailedException if an error occurs
111      */

112     public synchronized void sendMessage(Message message,
113                                          Address[] addresses)
114         throws MessagingException, SendFailedException
115     {
116
117         // Variables
118
String JavaDoc ehlo;
119
120         // Store Message and addresses
121
mimeMessage = (MimeMessage) message;
122         this.addresses = addresses;
123
124         // Greeting
125
readServerResponse();
126
127         // EHLO/HELO
128
ehlo = session.getProperty("mail.smtp.ehlo");
129         if (ehlo != null && ehlo.toUpperCase().equals("FALSE")) {
130             helo(getLocalHost());
131         }
132         else if (ehlo(getLocalHost()) == false) {
133             helo(getLocalHost());
134         }
135
136         // Send Header
137
mailFrom();
138         rcptTo();
139
140         // Send Data
141
try {
142             mimeMessage.writeTo(data());
143         }
144         catch (IOException ioe) {
145             throw new MessagingException("Unable to send message", ioe);
146         }
147         finishData();
148
149         notifyTransportListeners();
150
151     }
152
153
154     /**
155      * Description of the Method.
156      *
157      * @exception MessagingException Description of Exception
158      */

159     public synchronized void connect()
160         throws MessagingException
161     {
162
163         // Variables
164
String JavaDoc host;
165         String JavaDoc user;
166         String JavaDoc port;
167
168         // Check for Properties
169
host = session.getProperty("mail.smtp.host");
170         user = session.getProperty("mail.smtp.user");
171         port = session.getProperty("mail.smtp.port");
172
173         // Check if Host Set
174
if (host == null) {
175             throw new MessagingException("No SMTP host has been " +
176                 "set (mail.smtp.host)");
177         }
178
179         // Connect
180
// Check if Port Set
181
if (port != null) {
182             int connectPort = Integer.parseInt(port);
183             connect(host, connectPort, user, null);
184         }
185         else {
186             connect(host, user, null);
187         }
188
189     }
190
191 // private void checkConnected()
192
// {
193
// // TODO
194
// }
195

196     /**
197      * Close our streams and sockets. This will be called by
198      * the superclass after our sendMessage() returns.
199      *
200      * @exception MessagingException if an error occurs
201      */

202     public synchronized void close()
203         throws MessagingException
204     {
205         // TODO
206

207         // Check if connection is Open
208
if (isConnected()) {
209             closeConnection();
210         }
211
212         super.close();
213
214     }
215
216
217     /**
218      * Delegates to superclass method.
219      *
220      * @return true if connected.
221      */

222     public synchronized boolean isConnected()
223     {
224         return super.isConnected();
225     }
226
227
228     //-------------------------------------------------------------
229
// Methods ----------------------------------------------------
230
//-------------------------------------------------------------
231

232     /**
233      * Describe <code>protocolConnect</code> method here.
234      *
235      * @param host the SMTP server to connect to
236      * @param port the port to attempt connection on
237      * @param user the user to connect as
238      * @param password the password to use for authenticating user
239      * @return true if the connection attempt succeeds.
240      * @exception MessagingException if an error occurs
241      */

242     protected boolean protocolConnect(String JavaDoc host, int port, String JavaDoc user,
243                                       String JavaDoc password)
244         throws MessagingException
245     {
246
247         int connectPort;
248
249         try {
250             if (port == -1) {
251                 connectPort = 25;
252             }
253             else {
254                 connectPort = port;
255             }
256
257             // Open Server
258
openServer(host, connectPort);
259
260             // TODO: Currently doesn't support user authentication
261

262         }
263         catch (Exception JavaDoc e) {
264             throw new MessagingException("Unable to connect", e);
265         }
266
267         // Return Valid Connection
268
return true;
269     }
270
271
272     private void openServer(String JavaDoc host, int port)
273         throws MessagingException
274     {
275
276         // Variables
277
String JavaDoc timeout;
278
279         // Create Server Socket
280
try {
281             socket = new Socket(host, port);
282         }
283         catch (IOException ioe) {
284             throw new MessagingException("Unable to open server", ioe);
285         }
286
287         // Check for "mail.smtp.timeout" option
288
timeout = session.getProperty("mail.smtp.timeout");
289         if (timeout != null) {
290             try {
291                 socket.setSoTimeout(Integer.parseInt(timeout));
292             }
293             catch (SocketException se) {
294                 throw new MessagingException("Unable to open server", se);
295             }
296         }
297
298         try {
299             InputStream sin = socket.getInputStream();
300             OutputStream sout = socket.getOutputStream();
301             serverInput = new BufferedReader(new InputStreamReader(sin));
302             serverOutput = new CRLFOutputStream(sout);
303         }
304         catch (IOException ioe) {
305             throw new MessagingException("Unable to open server", ioe);
306         }
307
308     }
309
310
311     /** Inform anybody listening to us how the message sending resulted. */
312     private void notifyTransportListeners()
313     {
314         Address[] validSentAddr = new Address[validSentAddrList.size()];
315         validSentAddrList.copyInto(validSentAddr);
316         Address[] validUnsentAddr = new Address[validUnsentAddrList.size()];
317         validUnsentAddrList.copyInto(validUnsentAddr);
318         Address[] invalidAddr = new Address[invalidAddrList.size()];
319         invalidAddrList.copyInto(invalidAddr);
320
321         notifyTransportListeners(deliveryStatus, validSentAddr, validUnsentAddr,
322             invalidAddr, mimeMessage);
323     }
324
325
326     private void closeConnection()
327     {
328         try {
329             simpleCommand("QUIT");
330
331             // Close Streams
332
serverOutput.close();
333             serverInput.close();
334             socket.close();
335         }
336         // some servers don't end the session properly, causing different errors,
337
// none of which matter at this point
338
catch (Throwable JavaDoc t) {
339         }
340
341     }
342
343
344     /**
345      * Get localhost domain for the helo/ehlo handshake. This value
346      * can be set using the "mail.smtp.localhost" property if
347      * InetAddress.getLocalHost() doesn't provide the correct information.
348      *
349      * @return local host
350      * @returns Local host domain. If unknown return ""
351      */

352     private String JavaDoc getLocalHost()
353     {
354
355         // LocalHost is calculated as follows:
356
// 1) Check for mail.smtp.localhost property
357
// 2) Generate from InetAddress.getLocalHost()
358
// 3) Otherwise, return empty ""
359

360         // Check for Local Host String
361
if (localHostName != null) {
362             return localHostName;
363         }
364
365         // Check For mail.smtp.localhost property
366
localHostName = session.getProperty("mail.smtp.localhost");
367         if (localHostName != null) {
368             return localHostName;
369         }
370
371         // Determine Localhost
372
try {
373             localHostName = InetAddress.getLocalHost().getHostName();
374             return localHostName;
375         }
376         catch (UnknownHostException e) {
377             // not sure what circumstances can cause this to happen.
378
}
379
380         // Return Unknown
381
return "";
382     }
383
384
385     /**
386      * Checks lines of input from the server and returns true
387      * if the server has finished sending this reply to us.
388      * This may happen in response to EHLO or HELP commands.
389      *
390      * @param value a line of input from the SMTP server
391      * @return true if the server is waiting for us now.
392      */

393     private boolean isNotLastLine(String JavaDoc value)
394     {
395
396         // Check for space after code
397
if (value.charAt(3) == ' ') {
398             return false;
399         }
400
401         return true;
402     }
403
404     // private void issueCommand(String value1, int value2) throws MessagingException
405
//{
406
// TODO
407
//}
408

409     /**
410      * Send a ELHO command to the SMTP server. Since it is possible
411      * that the SMTP server doesn't support extensions, false would be
412      * returned in such a scenario. Otherwise, if there is a problem,
413      * an exception should be thrown.
414      *
415      * @param domain EHLO domain for handshaking
416      * @return Description of the Returned Value
417      * @exception MessagingException Problem has occurred
418      * @returns true if SMTP extensions are supported, false otherwise
419      */

420     private boolean ehlo(String JavaDoc domain)
421         throws MessagingException
422     {
423
424         String JavaDoc command = "EHLO " + domain;
425
426         int response = simpleCommand(command);
427
428         if (response != 250) {
429             return false;
430         }
431
432         return true;
433     }
434
435
436     /**
437      * Send a HELO command to the SMTP server. If there is a problem,
438      * an exception should be thrown.
439      *
440      * @param domain HELO domain for handshaking
441      * @exception MessagingException Problem has occurred
442      */

443     private void helo(String JavaDoc domain)
444         throws MessagingException
445     {
446
447         // Variables
448
int response;
449         String JavaDoc command;
450
451         // Construct Command
452
command = "HELO " + domain;
453
454         // Command
455
response = simpleCommand(command);
456
457     }
458
459
460     /**
461      * Offers our mail sender(s) to the SMTP server.
462      *
463      * @exception SendFailedException if no valid message sender is found.
464      * @exception MessagingException for any other error cause.
465      */

466     private void mailFrom()
467         throws SendFailedException, MessagingException
468     {
469
470         Address from;
471         String JavaDoc addressFromProperty;
472         String JavaDoc mailFrom;
473         int response;
474
475         addressFromProperty = session.getProperty("mail.smtp.from");
476         if (addressFromProperty != null) {
477             from = new InternetAddress(addressFromProperty);
478         }
479         else if ((from = mimeMessage.getFrom()[0]) != null) {
480             from = mimeMessage.getFrom()[0];
481         }
482         else {
483             deliveryStatus = TransportEvent.MESSAGE_NOT_DELIVERED;
484             throw new SendFailedException("No valid message sender specified");
485         }
486
487         mailFrom = "MAIL FROM: <" + from + ">";
488         response = simpleCommand(mailFrom);
489
490         if (response != 250) {
491             deliveryStatus = TransportEvent.MESSAGE_NOT_DELIVERED;
492             throw new SendFailedException("Sender " + from + " was rejected: "
493                  + response);
494         }
495
496     }
497
498
499     /**
500      * Send list of mail recipients to SMTP server. Keeps track
501      * of which messages are valid/invalid.
502      *
503      * @exception SendFailedException Problem has occurred
504      * @exception MessagingException Description of Exception
505      */

506     private void rcptTo()
507         throws SendFailedException, MessagingException
508     {
509
510         // Process each Address
511
for (int i = 0; i < addresses.length; i++) {
512
513             String JavaDoc rcptTo = "RCPT TO: <" + addresses[i] + ">";
514             int response = simpleCommand(rcptTo);
515
516             if (response == 250) {
517                 validSentAddrList.insertElementAt(addresses[i], 0);
518             }
519             else {
520                 invalidAddrList.insertElementAt(addresses[i], 0);
521             }
522         }
523
524         if (validSentAddrList.size() == 0) {
525             deliveryStatus = TransportEvent.MESSAGE_NOT_DELIVERED;
526             throw new SendFailedException("No valid mail recipients specified");
527         }
528
529     }
530
531
532     private OutputStream data()
533         throws MessagingException
534     {
535
536         int response = simpleCommand("DATA");
537
538         return new SMTPOutputStream(serverOutput);
539     }
540
541
542     /**
543      * Send end-of-data tag to SMTP server.
544      *
545      * @exception MessagingException Problem has occurred
546      */

547     private void finishData()
548         throws MessagingException
549     {
550
551         String JavaDoc dataEnd = "\r\n.\r\n";
552
553         int response = simpleCommand(dataEnd);
554
555         /* Process address Lists depending on result of DATA command. */
556         if (response != 250) {
557             /* The following method is not present in JDK1.1
558       validUnsentAddrList.addAll(validSentAddrList);
559       */

560             int size = validSentAddrList.size();
561             validUnsentAddrList.ensureCapacity(size);
562             for (int i = 0; i < size; i++) {
563                 validUnsentAddrList.addElement(validSentAddrList.elementAt(i));
564             }
565
566             /* The following method is not present in JDK1.1
567       validSentAddrList.clear();
568       */

569             validSentAddrList.removeAllElements();
570
571             deliveryStatus = TransportEvent.MESSAGE_NOT_DELIVERED;
572         }
573         else {
574             if (invalidAddrList.size() == 0) {
575                 deliveryStatus = TransportEvent.MESSAGE_DELIVERED;
576             }
577             else {
578                 deliveryStatus = TransportEvent.MESSAGE_PARTIALLY_DELIVERED;
579             }
580         }
581
582     }
583
584
585     private String JavaDoc normalizeAddress(String JavaDoc address)
586     {
587         return null; // TODO
588
}
589
590
591     private int readServerResponse()
592         throws MessagingException
593     {
594
595         // TODO: Figure out if this method should be processing all
596
// the responses from the SMTP server, or only a single line
597
// at a time leaving the looping to the caller. For now,
598
// method processes all responses.
599

600         // Variables
601
String JavaDoc code;
602         boolean end;
603
604         // Read Each Line of Input From Server
605

606         try {
607             lastServerResponse = serverInput.readLine();
608             while (isNotLastLine(lastServerResponse)) {
609                 lastServerResponse = serverInput.readLine();
610             }
611         }
612         catch (IOException ioe) {
613             throw new MessagingException("Unable to read server response", ioe);
614         }
615
616         Session.log("SMTP< " + lastServerResponse);
617         code = lastServerResponse.substring(0, 3);
618
619         // Return Code
620
return Integer.parseInt(code);
621     }
622
623
624     /**
625      * Write a command to the SMTP server and parse the response.
626      *
627      * @param command Command to send
628      * @return Description of the Returned Value
629      * @exception MessagingException Problem has occurred
630      * @returns Return code from SMTP server
631      */

632     private int simpleCommand(String JavaDoc command)
633         throws MessagingException
634     {
635
636         // Send Command
637
sendCommand(command);
638
639         // Return Response
640
return readServerResponse();
641     }
642
643
644     /**
645      * Send command to SMTP server.
646      *
647      * @param command Command to send
648      * @exception MessagingException Problem has occurred
649      */

650     private void sendCommand(String JavaDoc command)
651         throws MessagingException
652     {
653         try {
654             serverOutput.write((command + CRLF).getBytes());
655         }
656         catch (IOException e) {
657             throw new MessagingException("Problem writing to server", e);
658         }
659
660         Session.log("SMTP> " + command);
661
662     }
663
664 // private boolean supportsAuthentication(String value)
665
// {
666
// return false; // TODO
667
// }
668

669 // private boolean supportsExtension(String value)
670
// {
671
// return false; // TODO
672
// }
673

674
675 }
676
677
Popular Tags