001: /*
002: * Copyright 2001-2005 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.net.smtp;
017:
018: import java.io.IOException;
019: import java.io.Writer;
020: import java.net.InetAddress;
021: import org.apache.commons.net.io.DotTerminatedMessageWriter;
022:
023: /***
024: * SMTPClient encapsulates all the functionality necessary to send files
025: * through an SMTP server. This class takes care of all
026: * low level details of interacting with an SMTP server and provides
027: * a convenient higher level interface. As with all classes derived
028: * from {@link org.apache.commons.net.SocketClient},
029: * you must first connect to the server with
030: * {@link org.apache.commons.net.SocketClient#connect connect }
031: * before doing anything, and finally
032: * {@link org.apache.commons.net.SocketClient#disconnect disconnect }
033: * after you're completely finished interacting with the server.
034: * Then you need to check the SMTP reply code to see if the connection
035: * was successful. For example:
036: * <pre>
037: * try {
038: * int reply;
039: * client.connect("mail.foobar.com");
040: * System.out.print(client.getReplyString());
041: *
042: * // After connection attempt, you should check the reply code to verify
043: * // success.
044: * reply = client.getReplyCode();
045: *
046: * if(!SMTPReply.isPositiveCompletion(reply)) {
047: * client.disconnect();
048: * System.err.println("SMTP server refused connection.");
049: * System.exit(1);
050: * }
051: *
052: * // Do useful stuff here.
053: * ...
054: * } catch(IOException e) {
055: * if(client.isConnected()) {
056: * try {
057: * client.disconnect();
058: * } catch(IOException f) {
059: * // do nothing
060: * }
061: * }
062: * System.err.println("Could not connect to server.");
063: * e.printStackTrace();
064: * System.exit(1);
065: * }
066: * </pre>
067: * <p>
068: * Immediately after connecting is the only real time you need to check the
069: * reply code (because connect is of type void). The convention for all the
070: * SMTP command methods in SMTPClient is such that they either return a
071: * boolean value or some other value.
072: * The boolean methods return true on a successful completion reply from
073: * the SMTP server and false on a reply resulting in an error condition or
074: * failure. The methods returning a value other than boolean return a value
075: * containing the higher level data produced by the SMTP command, or null if a
076: * reply resulted in an error condition or failure. If you want to access
077: * the exact SMTP reply code causing a success or failure, you must call
078: * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode } after
079: * a success or failure.
080: * <p>
081: * You should keep in mind that the SMTP server may choose to prematurely
082: * close a connection for various reasons. The SMTPClient class will detect a
083: * premature SMTP server connection closing when it receives a
084: * {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE }
085: * response to a command.
086: * When that occurs, the method encountering that reply will throw
087: * an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException}
088: * .
089: * <code>SMTPConectionClosedException</code>
090: * is a subclass of <code> IOException </code> and therefore need not be
091: * caught separately, but if you are going to catch it separately, its
092: * catch block must appear before the more general <code> IOException </code>
093: * catch block. When you encounter an
094: * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException}
095: * , you must disconnect the connection with
096: * {@link #disconnect disconnect() } to properly clean up the
097: * system resources used by SMTPClient. Before disconnecting, you may check
098: * the last reply code and text with
099: * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode },
100: * {@link org.apache.commons.net.smtp.SMTP#getReplyString getReplyString },
101: * and
102: * {@link org.apache.commons.net.smtp.SMTP#getReplyStrings getReplyStrings}.
103: * <p>
104: * Rather than list it separately for each method, we mention here that
105: * every method communicating with the server and throwing an IOException
106: * can also throw a
107: * {@link org.apache.commons.net.MalformedServerReplyException}
108: * , which is a subclass
109: * of IOException. A MalformedServerReplyException will be thrown when
110: * the reply received from the server deviates enough from the protocol
111: * specification that it cannot be interpreted in a useful manner despite
112: * attempts to be as lenient as possible.
113: * <p>
114: * <p>
115: * @author Daniel F. Savarese
116: * @see SMTP
117: * @see SimpleSMTPHeader
118: * @see RelayPath
119: * @see SMTPConnectionClosedException
120: * @see org.apache.commons.net.MalformedServerReplyException
121: ***/
122:
123: public class SMTPClient extends SMTP {
124:
125: /*
126: * Default SMTPClient constructor. Creates a new SMTPClient instance.
127: */
128: //public SMTPClient() { }
129:
130: /***
131: * At least one SMTPClient method ({@link #sendMessageData sendMessageData })
132: * does not complete the entire sequence of SMTP commands to complete a
133: * transaction. These types of commands require some action by the
134: * programmer after the reception of a positive intermediate command.
135: * After the programmer's code completes its actions, it must call this
136: * method to receive the completion reply from the server and verify the
137: * success of the entire transaction.
138: * <p>
139: * For example,
140: * <pre>
141: * writer = client.sendMessage();
142: * if(writer == null) // failure
143: * return false;
144: * header =
145: * new SimpleSMTPHeader("foobar@foo.com", "foo@foobar.com", "Re: Foo");
146: * writer.write(header.toString());
147: * writer.write("This is just a test");
148: * writer.close();
149: * if(!client.completePendingCommand()) // failure
150: * return false;
151: * </pre>
152: * <p>
153: * @return True if successfully completed, false if not.
154: * @exception SMTPConnectionClosedException
155: * If the SMTP server prematurely closes the connection as a result
156: * of the client being idle or some other reason causing the server
157: * to send SMTP reply code 421. This exception may be caught either
158: * as an IOException or independently as itself.
159: * @exception IOException If an I/O error occurs while either sending a
160: * command to the server or receiving a reply from the server.
161: ***/
162: public boolean completePendingCommand() throws IOException {
163: return SMTPReply.isPositiveCompletion(getReply());
164: }
165:
166: /***
167: * Login to the SMTP server by sending the HELO command with the
168: * given hostname as an argument. Before performing any mail commands,
169: * you must first login.
170: * <p>
171: * @param hostname The hostname with which to greet the SMTP server.
172: * @return True if successfully completed, false if not.
173: * @exception SMTPConnectionClosedException
174: * If the SMTP server prematurely closes the connection as a result
175: * of the client being idle or some other reason causing the server
176: * to send SMTP reply code 421. This exception may be caught either
177: * as an IOException or independently as itself.
178: * @exception IOException If an I/O error occurs while either sending a
179: * command to the server or receiving a reply from the server.
180: ***/
181: public boolean login(String hostname) throws IOException {
182: return SMTPReply.isPositiveCompletion(helo(hostname));
183: }
184:
185: /***
186: * Login to the SMTP server by sending the HELO command with the
187: * client hostname as an argument. Before performing any mail commands,
188: * you must first login.
189: * <p>
190: * @return True if successfully completed, false if not.
191: * @exception SMTPConnectionClosedException
192: * If the SMTP server prematurely closes the connection as a result
193: * of the client being idle or some other reason causing the server
194: * to send SMTP reply code 421. This exception may be caught either
195: * as an IOException or independently as itself.
196: * @exception IOException If an I/O error occurs while either sending a
197: * command to the server or receiving a reply from the server.
198: ***/
199: public boolean login() throws IOException {
200: String name;
201: InetAddress host;
202:
203: host = getLocalAddress();
204: name = host.getHostName();
205:
206: if (name == null)
207: return false;
208:
209: return SMTPReply.isPositiveCompletion(helo(name));
210: }
211:
212: /***
213: * Set the sender of a message using the SMTP MAIL command, specifying
214: * a reverse relay path. The sender must be set first before any
215: * recipients may be specified, otherwise the mail server will reject
216: * your commands.
217: * <p>
218: * @param path The reverse relay path pointing back to the sender.
219: * @return True if successfully completed, false if not.
220: * @exception SMTPConnectionClosedException
221: * If the SMTP server prematurely closes the connection as a result
222: * of the client being idle or some other reason causing the server
223: * to send SMTP reply code 421. This exception may be caught either
224: * as an IOException or independently as itself.
225: * @exception IOException If an I/O error occurs while either sending a
226: * command to the server or receiving a reply from the server.
227: ***/
228: public boolean setSender(RelayPath path) throws IOException {
229: return SMTPReply.isPositiveCompletion(mail(path.toString()));
230: }
231:
232: /***
233: * Set the sender of a message using the SMTP MAIL command, specifying
234: * the sender's email address. The sender must be set first before any
235: * recipients may be specified, otherwise the mail server will reject
236: * your commands.
237: * <p>
238: * @param address The sender's email address.
239: * @return True if successfully completed, false if not.
240: * @exception SMTPConnectionClosedException
241: * If the SMTP server prematurely closes the connection as a result
242: * of the client being idle or some other reason causing the server
243: * to send SMTP reply code 421. This exception may be caught either
244: * as an IOException or independently as itself.
245: * @exception IOException If an I/O error occurs while either sending a
246: * command to the server or receiving a reply from the server.
247: ***/
248: public boolean setSender(String address) throws IOException {
249: return SMTPReply
250: .isPositiveCompletion(mail("<" + address + ">"));
251: }
252:
253: /***
254: * Add a recipient for a message using the SMTP RCPT command, specifying
255: * a forward relay path. The sender must be set first before any
256: * recipients may be specified, otherwise the mail server will reject
257: * your commands.
258: * <p>
259: * @param path The forward relay path pointing to the recipient.
260: * @return True if successfully completed, false if not.
261: * @exception SMTPConnectionClosedException
262: * If the SMTP server prematurely closes the connection as a result
263: * of the client being idle or some other reason causing the server
264: * to send SMTP reply code 421. This exception may be caught either
265: * as an IOException or independently as itself.
266: * @exception IOException If an I/O error occurs while either sending a
267: * command to the server or receiving a reply from the server.
268: ***/
269: public boolean addRecipient(RelayPath path) throws IOException {
270: return SMTPReply.isPositiveCompletion(rcpt(path.toString()));
271: }
272:
273: /***
274: * Add a recipient for a message using the SMTP RCPT command, the
275: * recipient's email address. The sender must be set first before any
276: * recipients may be specified, otherwise the mail server will reject
277: * your commands.
278: * <p>
279: * @param address The recipient's email address.
280: * @return True if successfully completed, false if not.
281: * @exception SMTPConnectionClosedException
282: * If the SMTP server prematurely closes the connection as a result
283: * of the client being idle or some other reason causing the server
284: * to send SMTP reply code 421. This exception may be caught either
285: * as an IOException or independently as itself.
286: * @exception IOException If an I/O error occurs while either sending a
287: * command to the server or receiving a reply from the server.
288: ***/
289: public boolean addRecipient(String address) throws IOException {
290: return SMTPReply
291: .isPositiveCompletion(rcpt("<" + address + ">"));
292: }
293:
294: /***
295: * Send the SMTP DATA command in preparation to send an email message.
296: * This method returns a DotTerminatedMessageWriter instance to which
297: * the message can be written. Null is returned if the DATA command
298: * fails.
299: * <p>
300: * You must not issue any commands to the SMTP server (i.e., call any
301: * (other methods) until you finish writing to the returned Writer
302: * instance and close it. The SMTP protocol uses the same stream for
303: * issuing commands as it does for returning results. Therefore the
304: * returned Writer actually writes directly to the SMTP connection.
305: * After you close the writer, you can execute new commands. If you
306: * do not follow these requirements your program will not work properly.
307: * <p>
308: * You can use the provided
309: * {@link org.apache.commons.net.smtp.SimpleSMTPHeader}
310: * class to construct a bare minimum header.
311: * To construct more complicated headers you should
312: * refer to RFC 822. When the Java Mail API is finalized, you will be
313: * able to use it to compose fully compliant Internet text messages.
314: * The DotTerminatedMessageWriter takes care of doubling line-leading
315: * dots and ending the message with a single dot upon closing, so all
316: * you have to worry about is writing the header and the message.
317: * <p>
318: * Upon closing the returned Writer, you need to call
319: * {@link #completePendingCommand completePendingCommand() }
320: * to finalize the transaction and verify its success or failure from
321: * the server reply.
322: * <p>
323: * @return A DotTerminatedMessageWriter to which the message (including
324: * header) can be written. Returns null if the command fails.
325: * @exception SMTPConnectionClosedException
326: * If the SMTP server prematurely closes the connection as a result
327: * of the client being idle or some other reason causing the server
328: * to send SMTP reply code 421. This exception may be caught either
329: * as an IOException or independently as itself.
330: * @exception IOException If an I/O error occurs while either sending a
331: * command to the server or receiving a reply from the server.
332: ***/
333: public Writer sendMessageData() throws IOException {
334: if (!SMTPReply.isPositiveIntermediate(data()))
335: return null;
336:
337: return new DotTerminatedMessageWriter(_writer);
338: }
339:
340: /***
341: * A convenience method for sending short messages. This method fetches
342: * the Writer returned by {@link #sendMessageData sendMessageData() }
343: * and writes the specified String to it. After writing the message,
344: * this method calls {@link #completePendingCommand completePendingCommand() }
345: * to finalize the transaction and returns
346: * its success or failure.
347: * <p>
348: * @param message The short email message to send.
349: * @return True if successfully completed, false if not.
350: * @exception SMTPConnectionClosedException
351: * If the SMTP server prematurely closes the connection as a result
352: * of the client being idle or some other reason causing the server
353: * to send SMTP reply code 421. This exception may be caught either
354: * as an IOException or independently as itself.
355: * @exception IOException If an I/O error occurs while either sending a
356: * command to the server or receiving a reply from the server.
357: ***/
358: public boolean sendShortMessageData(String message)
359: throws IOException {
360: Writer writer;
361:
362: writer = sendMessageData();
363:
364: if (writer == null)
365: return false;
366:
367: writer.write(message);
368: writer.close();
369:
370: return completePendingCommand();
371: }
372:
373: /***
374: * A convenience method for a sending short email without having to
375: * explicitly set the sender and recipient(s). This method
376: * sets the sender and recipient using
377: * {@link #setSender setSender } and
378: * {@link #addRecipient addRecipient }, and then sends the
379: * message using {@link #sendShortMessageData sendShortMessageData }.
380: * <p>
381: * @param sender The email address of the sender.
382: * @param recipient The email address of the recipient.
383: * @param message The short email message to send.
384: * @return True if successfully completed, false if not.
385: * @exception SMTPConnectionClosedException
386: * If the SMTP server prematurely closes the connection as a result
387: * of the client being idle or some other reason causing the server
388: * to send SMTP reply code 421. This exception may be caught either
389: * as an IOException or independently as itself.
390: * @exception IOException If an I/O error occurs while either sending a
391: * command to the server or receiving a reply from the server.
392: ***/
393: public boolean sendSimpleMessage(String sender, String recipient,
394: String message) throws IOException {
395: if (!setSender(sender))
396: return false;
397:
398: if (!addRecipient(recipient))
399: return false;
400:
401: return sendShortMessageData(message);
402: }
403:
404: /***
405: * A convenience method for a sending short email without having to
406: * explicitly set the sender and recipient(s). This method
407: * sets the sender and recipients using
408: * {@link #setSender setSender } and
409: * {@link #addRecipient addRecipient }, and then sends the
410: * message using {@link #sendShortMessageData sendShortMessageData }.
411: * <p>
412: * @param sender The email address of the sender.
413: * @param recipients An array of recipient email addresses.
414: * @param message The short email message to send.
415: * @return True if successfully completed, false if not.
416: * @exception SMTPConnectionClosedException
417: * If the SMTP server prematurely closes the connection as a result
418: * of the client being idle or some other reason causing the server
419: * to send SMTP reply code 421. This exception may be caught either
420: * as an IOException or independently as itself.
421: * @exception IOException If an I/O error occurs while either sending a
422: * command to the server or receiving a reply from the server.
423: ***/
424: public boolean sendSimpleMessage(String sender,
425: String[] recipients, String message) throws IOException {
426: boolean oneSuccess = false;
427: int count;
428:
429: if (!setSender(sender))
430: return false;
431:
432: for (count = 0; count < recipients.length; count++) {
433: if (addRecipient(recipients[count]))
434: oneSuccess = true;
435: }
436:
437: if (!oneSuccess)
438: return false;
439:
440: return sendShortMessageData(message);
441: }
442:
443: /***
444: * Logout of the SMTP server by sending the QUIT command.
445: * <p>
446: * @return True if successfully completed, false if not.
447: * @exception SMTPConnectionClosedException
448: * If the SMTP server prematurely closes the connection as a result
449: * of the client being idle or some other reason causing the server
450: * to send SMTP reply code 421. This exception may be caught either
451: * as an IOException or independently as itself.
452: * @exception IOException If an I/O error occurs while either sending a
453: * command to the server or receiving a reply from the server.
454: ***/
455: public boolean logout() throws IOException {
456: return SMTPReply.isPositiveCompletion(quit());
457: }
458:
459: /***
460: * Aborts the current mail transaction, resetting all server stored
461: * sender, recipient, and mail data, cleaing all buffers and tables.
462: * <p>
463: * @return True if successfully completed, false if not.
464: * @exception SMTPConnectionClosedException
465: * If the SMTP server prematurely closes the connection as a result
466: * of the client being idle or some other reason causing the server
467: * to send SMTP reply code 421. This exception may be caught either
468: * as an IOException or independently as itself.
469: * @exception IOException If an I/O error occurs while either sending a
470: * command to the server or receiving a reply from the server.
471: ***/
472: public boolean reset() throws IOException {
473: return SMTPReply.isPositiveCompletion(rset());
474: }
475:
476: /***
477: * Verify that a username or email address is valid, i.e., that mail
478: * can be delivered to that mailbox on the server.
479: * <p>
480: * @param username The username or email address to validate.
481: * @return True if the username is valid, false if not.
482: * @exception SMTPConnectionClosedException
483: * If the SMTP server prematurely closes the connection as a result
484: * of the client being idle or some other reason causing the server
485: * to send SMTP reply code 421. This exception may be caught either
486: * as an IOException or independently as itself.
487: * @exception IOException If an I/O error occurs while either sending a
488: * command to the server or receiving a reply from the server.
489: ***/
490: public boolean verify(String username) throws IOException {
491: int result;
492:
493: result = vrfy(username);
494:
495: return (result == SMTPReply.ACTION_OK || result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD);
496: }
497:
498: /***
499: * Fetches the system help information from the server and returns the
500: * full string.
501: * <p>
502: * @return The system help string obtained from the server. null if the
503: * information could not be obtained.
504: * @exception SMTPConnectionClosedException
505: * If the SMTP server prematurely closes the connection as a result
506: * of the client being idle or some other reason causing the server
507: * to send SMTP reply code 421. This exception may be caught either
508: * as an IOException or independently as itself.
509: * @exception IOException If an I/O error occurs while either sending a
510: * command to the server or receiving a reply from the server.
511: ***/
512: public String listHelp() throws IOException {
513: if (SMTPReply.isPositiveCompletion(help()))
514: return getReplyString();
515: return null;
516: }
517:
518: /***
519: * Fetches the help information for a given command from the server and
520: * returns the full string.
521: * <p>
522: * @param command The command on which to ask for help.
523: * @return The command help string obtained from the server. null if the
524: * information could not be obtained.
525: * @exception SMTPConnectionClosedException
526: * If the SMTP server prematurely closes the connection as a result
527: * of the client being idle or some other reason causing the server
528: * to send SMTP reply code 421. This exception may be caught either
529: * as an IOException or independently as itself.
530: * @exception IOException If an I/O error occurs while either sending a
531: * command to the server or receiving a reply from the server.
532: ***/
533: public String listHelp(String command) throws IOException {
534: if (SMTPReply.isPositiveCompletion(help(command)))
535: return getReplyString();
536: return null;
537: }
538:
539: /***
540: * Sends a NOOP command to the SMTP server. This is useful for preventing
541: * server timeouts.
542: * <p>
543: * @return True if successfully completed, false if not.
544: * @exception SMTPConnectionClosedException
545: * If the SMTP server prematurely closes the connection as a result
546: * of the client being idle or some other reason causing the server
547: * to send SMTP reply code 421. This exception may be caught either
548: * as an IOException or independently as itself.
549: * @exception IOException If an I/O error occurs while either sending a
550: * command to the server or receiving a reply from the server.
551: ***/
552: public boolean sendNoOp() throws IOException {
553: return SMTPReply.isPositiveCompletion(noop());
554: }
555:
556: }
|