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