1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.geronimo.javamail.transport.nntp;
21
22 import java.io.PrintStream;
23 import java.util.ArrayList;
24
25 import javax.mail.Address;
26 import javax.mail.Message;
27 import javax.mail.MessagingException;
28 import javax.mail.Session;
29 import javax.mail.Transport;
30 import javax.mail.URLName;
31 import javax.mail.event.TransportEvent;
32 import javax.mail.internet.InternetAddress;
33 import javax.mail.internet.MimeMessage;
34 import javax.mail.internet.NewsAddress;
35
36 import org.apache.geronimo.javamail.util.ProtocolProperties;
37
38 /**
39 * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
40 * <p/> There is no way to indicate failure for a given recipient (it's possible
41 * to have a recipient address rejected). The sun impl throws exceptions even if
42 * others successful), but maybe we do a different way... <p/>
43 *
44 * @version $Rev: 673649 $ $Date: 2008-07-03 06:37:56 -0400 (Thu, 03 Jul 2008) $
45 */
46 public class NNTPTransport extends Transport {
47 /**
48 * property keys for protocol properties.
49 */
50 protected static final String NNTP_FROM = "from";
51
52 protected static final int DEFAULT_NNTP_PORT = 119;
53 protected static final int DEFAULT_NNTP_SSL_PORT = 563;
54
55 // our accessor for protocol properties and the holder of
56 // protocol-specific information
57 protected ProtocolProperties props;
58 // our active connection object (shared code with the NNTPStore).
59 protected NNTPConnection connection;
60
61 /**
62 * Normal constructor for an NNTPTransport() object. This constructor is
63 * used to build a transport instance for the "smtp" protocol.
64 *
65 * @param session
66 * The attached session.
67 * @param name
68 * An optional URLName object containing target information.
69 */
70 public NNTPTransport(Session session, URLName name) {
71 this(session, name, "nntp-post", DEFAULT_NNTP_PORT, false);
72 }
73
74 /**
75 * Common constructor used by the POP3Store and POP3SSLStore classes
76 * to do common initialization of defaults.
77 *
78 * @param session
79 * The host session instance.
80 * @param name
81 * The URLName of the target.
82 * @param protocol
83 * The protocol type ("pop3"). This helps us in
84 * retrieving protocol-specific session properties.
85 * @param defaultPort
86 * The default port used by this protocol. For pop3, this will
87 * be 110. The default for pop3 with ssl is 995.
88 * @param sslConnection
89 * Indicates whether an SSL connection should be used to initial
90 * contact the server. This is different from the STARTTLS
91 * support, which switches the connection to SSL after the
92 * initial startup.
93 */
94 protected NNTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
95 super(session, name);
96
97 // create the protocol property holder. This gives an abstraction over the different
98 // flavors of the protocol.
99 props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
100 // the connection manages connection for the transport
101 connection = new NNTPConnection(props);
102 }
103
104 /**
105 * Do the protocol connection for an NNTP transport. This handles server
106 * authentication, if possible. Returns false if unable to connect to the
107 * server.
108 *
109 * @param host
110 * The target host name.
111 * @param port
112 * The server port number.
113 * @param user
114 * The authentication user (if any).
115 * @param password
116 * The server password. Might not be sent directly if more
117 * sophisticated authentication is used.
118 *
119 * @return true if we were able to connect to the server properly, false for
120 * any failures.
121 * @exception MessagingException
122 */
123 protected boolean protocolConnect(String host, int port, String username, String password)
124 throws MessagingException {
125 // the connection pool handles all of the details here.
126 return connection.protocolConnect(host, port, username, password);
127 }
128
129
130 /**
131 * Send a message to multiple addressees.
132 *
133 * @param message
134 * The message we're sending.
135 * @param addresses
136 * An array of addresses to send to.
137 *
138 * @exception MessagingException
139 */
140 public void sendMessage(Message message, Address[] addresses) throws MessagingException {
141 if (!isConnected()) {
142 throw new IllegalStateException("Not connected");
143 }
144
145 if (!connection.isPostingAllowed()) {
146 throw new MessagingException("Posting disabled for host server");
147 }
148 // don't bother me w/ null messages or no addreses
149 if (message == null) {
150 throw new MessagingException("Null message");
151 }
152
153 // NNTP only handles instances of MimeMessage, not the more general
154 // message case.
155 if (!(message instanceof MimeMessage)) {
156 throw new MessagingException("NNTP can only send MimeMessages");
157 }
158
159 // need to sort the from value out from a variety of sources.
160 InternetAddress from = null;
161
162 Address[] fromAddresses = message.getFrom();
163
164 // If the message has a From address set, we just use that. Otherwise,
165 // we set a From using
166 // the property version, if available.
167 if (fromAddresses == null || fromAddresses.length == 0) {
168 // the from value can be set explicitly as a property
169 String defaultFrom = props.getProperty(NNTP_FROM);
170 if (defaultFrom == null) {
171 message.setFrom(new InternetAddress(defaultFrom));
172 }
173 }
174
175 // we must have a message list.
176 if (addresses == null || addresses.length == 0) {
177 throw new MessagingException("Null or empty address array");
178 }
179
180 boolean haveGroup = false;
181
182 // enforce the requirement that all of the targets are NewsAddress
183 // instances.
184 for (int i = 0; i < addresses.length; i++) {
185 if (!(addresses[i] instanceof NewsAddress)) {
186 throw new MessagingException("Illegal NewsAddress " + addresses[i]);
187 }
188 }
189
190 // event notifcation requires we send lists of successes and failures
191 // broken down by category.
192 // The categories are:
193 //
194 // 1) addresses successfully processed.
195 // 2) addresses deemed valid, but had a processing failure that
196 // prevented sending.
197 // 3) addressed deemed invalid (basically all other processing
198 // failures).
199 ArrayList sentAddresses = new ArrayList();
200 ArrayList unsentAddresses = new ArrayList();
201 ArrayList invalidAddresses = new ArrayList();
202
203 boolean sendFailure = false;
204
205 // now try to post this message to the different news groups.
206 for (int i = 0; i < addresses.length; i++) {
207 try {
208 // select the target news group
209 NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
210
211 if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
212 invalidAddresses.add(addresses[i]);
213 sendFailure = true;
214 } else {
215 // send data
216 connection.sendPost(message);
217 sentAddresses.add(addresses[i]);
218 }
219 } catch (MessagingException e) {
220 unsentAddresses.add(addresses[i]);
221 sendFailure = true;
222 }
223 }
224
225 // create our lists for notification and exception reporting from this
226 // point on.
227 Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
228 Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
229 Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
230
231 if (sendFailure) {
232 // did we deliver anything at all?
233 if (sent.length == 0) {
234 // notify of the error.
235 notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
236 } else {
237 // notify that we delivered at least part of this
238 notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
239 }
240
241 throw new MessagingException("Error posting NNTP message");
242 }
243
244 // notify our listeners of successful delivery.
245 notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
246 }
247
248 /**
249 * Close the connection. On completion, we'll be disconnected from the
250 * server and unable to send more data.
251 *
252 * @exception MessagingException
253 */
254 public void close() throws MessagingException {
255 // This is done to ensure proper event notification.
256 super.close();
257 // NB: We reuse the connection if asked to reconnect
258 connection.close();
259 }
260 }