1 /************************************************************************
2 * Copyright (c) 1999-2006 The Apache Software Foundation. *
3 * All rights reserved. *
4 * ------------------------------------------------------------------- *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you *
6 * may not use this file except in compliance with the License. You *
7 * may obtain a copy of the License at: *
8 * *
9 * http://www.apache.org/licenses/LICENSE-2.0 *
10 * *
11 * Unless required by applicable law or agreed to in writing, software *
12 * distributed under the License is distributed on an "AS IS" BASIS, *
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14 * implied. See the License for the specific language governing *
15 * permissions and limitations under the License. *
16 ***********************************************************************/
17
18 package org.apache.james.smtpserver;
19
20 import org.apache.avalon.framework.logger.AbstractLogEnabled;
21 import org.apache.james.Constants;
22 import org.apache.james.core.MailHeaders;
23 import org.apache.james.core.MailImpl;
24 import org.apache.james.fetchmail.ReaderInputStream;
25 import org.apache.james.util.CharTerminatedInputStream;
26 import org.apache.james.util.DotStuffingInputStream;
27 import org.apache.james.util.mail.dsn.DSNStatus;
28 import org.apache.james.util.watchdog.BytesReadResetInputStream;
29 import org.apache.mailet.MailAddress;
30 import org.apache.mailet.RFC2822Headers;
31 import org.apache.mailet.dates.RFC822DateFormat;
32
33 import javax.mail.MessagingException;
34
35 import java.io.ByteArrayInputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.SequenceInputStream;
39 import java.io.StringReader;
40 import java.util.Collection;
41 import java.util.Date;
42 import java.util.Enumeration;
43 import java.util.List;
44
45
46 /***
47 * handles DATA command
48 */
49 public class DataCmdHandler
50 extends AbstractLogEnabled
51 implements CommandHandler {
52
53 private final static String SOFTWARE_TYPE = "JAMES SMTP Server "
54 + Constants.SOFTWARE_VERSION;
55
56 /***
57 * Static RFC822DateFormat used to generate date headers
58 */
59 private final static RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
60
61
62
63 /***
64 * The mail attribute holding the SMTP AUTH user name, if any.
65 */
66 private final static String SMTP_AUTH_USER_ATTRIBUTE_NAME = "org.apache.james.SMTPAuthUser";
67
68 /***
69 * The character array that indicates termination of an SMTP connection
70 */
71 private final static char[] SMTPTerminator = { '\r', '\n', '.', '\r', '\n' };
72
73 /***
74 * process DATA command
75 *
76 * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
77 */
78 public void onCommand(SMTPSession session) {
79 doDATA(session, session.getCommandArgument());
80 }
81
82
83 /***
84 * Handler method called upon receipt of a DATA command.
85 * Reads in message data, creates header, and delivers to
86 * mail server service for delivery.
87 *
88 * @param session SMTP session object
89 * @param argument the argument passed in with the command by the SMTP client
90 */
91 private void doDATA(SMTPSession session, String argument) {
92 String responseString = null;
93 if ((argument != null) && (argument.length() > 0)) {
94 responseString = "500 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Unexpected argument provided with DATA command";
95 session.writeResponse(responseString);
96 }
97 if (!session.getState().containsKey(SMTPSession.SENDER)) {
98 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" No sender specified";
99 session.writeResponse(responseString);
100 } else if (!session.getState().containsKey(SMTPSession.RCPT_LIST)) {
101 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" No recipients specified";
102 session.writeResponse(responseString);
103 } else {
104 responseString = "354 Ok Send data ending with <CRLF>.<CRLF>";
105 session.writeResponse(responseString);
106 InputStream msgIn = new CharTerminatedInputStream(session.getInputStream(), SMTPTerminator);
107 try {
108 msgIn = new BytesReadResetInputStream(msgIn,
109 session.getWatchdog(),
110 session.getConfigurationData().getResetLength());
111
112
113
114 long maxMessageSize = session.getConfigurationData().getMaxMessageSize();
115 if (maxMessageSize > 0) {
116 if (getLogger().isDebugEnabled()) {
117 StringBuffer logBuffer =
118 new StringBuffer(128)
119 .append("Using SizeLimitedInputStream ")
120 .append(" with max message size: ")
121 .append(maxMessageSize);
122 getLogger().debug(logBuffer.toString());
123 }
124 msgIn = new SizeLimitedInputStream(msgIn, maxMessageSize);
125 }
126
127 msgIn = new DotStuffingInputStream(msgIn);
128
129 MailHeaders headers = new MailHeaders(msgIn);
130 headers = processMailHeaders(session, headers);
131 processMail(session, headers, msgIn);
132 headers = null;
133 } catch (MessagingException me) {
134
135 Exception e = me.getNextException();
136
137
138 if (e != null && e instanceof MessageSizeException) {
139
140
141
142
143 session.getState().put(SMTPSession.MESG_FAILED, Boolean.TRUE);
144
145
146 responseString = "552 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SYSTEM_MSG_TOO_BIG)+" Error processing message: "
147 + e.getMessage();
148 StringBuffer errorBuffer =
149 new StringBuffer(256)
150 .append("Rejected message from ")
151 .append(session.getState().get(SMTPSession.SENDER).toString())
152 .append(" from host ")
153 .append(session.getRemoteHost())
154 .append(" (")
155 .append(session.getRemoteIPAddress())
156 .append(") exceeding system maximum message size of ")
157 .append(session.getConfigurationData().getMaxMessageSize());
158 getLogger().error(errorBuffer.toString());
159 } else {
160 responseString = "451 "+DSNStatus.getStatus(DSNStatus.TRANSIENT,DSNStatus.UNDEFINED_STATUS)+" Error processing message: "
161 + me.getMessage();
162 getLogger().error("Unknown error occurred while processing DATA.", me);
163 }
164 session.writeResponse(responseString);
165 return;
166 } finally {
167 if (msgIn != null) {
168 try {
169 msgIn.close();
170 } catch (Exception e) {
171
172 }
173 msgIn = null;
174 }
175 }
176 }
177 }
178
179
180
181
182
183
184
185 private MailHeaders processMailHeaders(SMTPSession session, MailHeaders headers)
186 throws MessagingException {
187
188
189 if (!headers.isSet(RFC2822Headers.DATE)) {
190 headers.setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new Date()));
191 }
192 if (!headers.isSet(RFC2822Headers.FROM) && session.getState().get(SMTPSession.SENDER) != null) {
193 headers.setHeader(RFC2822Headers.FROM, session.getState().get(SMTPSession.SENDER).toString());
194 }
195
196
197
198
199
200 StringBuffer headerLineBuffer = new StringBuffer(512);
201
202 Enumeration headerLines = headers.getAllHeaderLines();
203 MailHeaders newHeaders = new MailHeaders();
204
205 headerLineBuffer.append(RFC2822Headers.RECEIVED + ": from ")
206 .append(session.getRemoteHost())
207 .append(" ([")
208 .append(session.getRemoteIPAddress())
209 .append("])");
210
211 newHeaders.addHeaderLine(headerLineBuffer.toString());
212 headerLineBuffer.delete(0, headerLineBuffer.length());
213
214 headerLineBuffer.append(" by ")
215 .append(session.getConfigurationData().getHelloName())
216 .append(" (")
217 .append(SOFTWARE_TYPE)
218 .append(") with SMTP ID ")
219 .append(session.getSessionID());
220
221 if (((Collection) session.getState().get(SMTPSession.RCPT_LIST)).size() == 1) {
222
223
224
225 newHeaders.addHeaderLine(headerLineBuffer.toString());
226 headerLineBuffer.delete(0, headerLineBuffer.length());
227 headerLineBuffer.append(" for <")
228 .append(((List) session.getState().get(SMTPSession.RCPT_LIST)).get(0).toString())
229 .append(">;");
230 newHeaders.addHeaderLine(headerLineBuffer.toString());
231 headerLineBuffer.delete(0, headerLineBuffer.length());
232 } else {
233
234 headerLineBuffer.append(";");
235 newHeaders.addHeaderLine(headerLineBuffer.toString());
236 headerLineBuffer.delete(0, headerLineBuffer.length());
237 }
238 headerLineBuffer = null;
239 newHeaders.addHeaderLine(" " + rfc822DateFormat.format(new Date()));
240
241
242 while (headerLines.hasMoreElements()) {
243 newHeaders.addHeaderLine((String) headerLines.nextElement());
244 }
245 return newHeaders;
246 }
247
248 /***
249 * Processes the mail message coming in off the wire. Reads the
250 * content and delivers to the spool.
251 *
252 * @param session SMTP session object
253 * @param headers the headers of the mail being read
254 * @param msgIn the stream containing the message content
255 */
256 private void processMail(SMTPSession session, MailHeaders headers, InputStream msgIn)
257 throws MessagingException {
258 ByteArrayInputStream headersIn = null;
259 MailImpl mail = null;
260 List recipientCollection = null;
261 try {
262 headersIn = new ByteArrayInputStream(headers.toByteArray());
263 recipientCollection = (List) session.getState().get(SMTPSession.RCPT_LIST);
264 mail =
265 new MailImpl(session.getConfigurationData().getMailServer().getId(),
266 (MailAddress) session.getState().get(SMTPSession.SENDER),
267 recipientCollection,
268 new SequenceInputStream(new SequenceInputStream(headersIn, msgIn),
269 new ReaderInputStream(new StringReader("\r\n"))));
270
271
272 if (session.getConfigurationData().getMaxMessageSize() > 0) {
273 mail.getMessageSize();
274 }
275 mail.setRemoteHost(session.getRemoteHost());
276 mail.setRemoteAddr(session.getRemoteIPAddress());
277 if (session.getUser() != null) {
278 mail.setAttribute(SMTP_AUTH_USER_ATTRIBUTE_NAME, session.getUser());
279 }
280 session.setMail(mail);
281 } catch (MessagingException me) {
282
283
284
285
286
287
288
289
290
291
292
293
294 if (mail != null) {
295 mail.dispose();
296 }
297 throw me;
298 } finally {
299 if (recipientCollection != null) {
300 recipientCollection.clear();
301 }
302 recipientCollection = null;
303 if (headersIn != null) {
304 try {
305 headersIn.close();
306 } catch (IOException ioe) {
307
308 }
309 }
310 headersIn = null;
311 }
312
313 }
314
315 }