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.configuration.Configurable;
23 import org.apache.avalon.framework.configuration.Configuration;
24 import org.apache.avalon.framework.configuration.ConfigurationException;
25 import org.apache.avalon.framework.logger.AbstractLogEnabled;
26 import org.apache.avalon.framework.service.ServiceException;
27 import org.apache.avalon.framework.service.ServiceManager;
28 import org.apache.avalon.framework.service.Serviceable;
29 import org.apache.james.services.DNSServer;
30 import org.apache.james.util.mail.dsn.DSNStatus;
31 import org.apache.mailet.MailAddress;
32
33 import java.util.Collection;
34 import java.util.Locale;
35 import java.util.StringTokenizer;
36
37 /***
38 * Handles MAIL command
39 */
40 public class MailCmdHandler
41 extends AbstractLogEnabled
42 implements CommandHandler,Configurable, Serviceable {
43
44 private final static String MAIL_OPTION_SIZE = "SIZE";
45
46 private final static String MESG_SIZE = "MESG_SIZE";
47
48 private boolean checkValidSenderDomain = false;
49
50 private boolean checkAuthClients = false;
51
52 private DNSServer dnsServer = null;
53
54 /***
55 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
56 */
57 public void configure(Configuration handlerConfiguration) throws ConfigurationException {
58 Configuration configuration = handlerConfiguration.getChild("checkValidSenderDomain",false);
59 if(configuration != null) {
60 checkValidSenderDomain = configuration.getValueAsBoolean();
61 if (checkValidSenderDomain && dnsServer == null) {
62 throw new ConfigurationException("checkValidSenderDomain enabled but no DNSServer service provided to SMTPServer");
63 }
64 }
65
66 Configuration configRelay = handlerConfiguration.getChild("checkAuthClients",false);
67 if(configRelay != null) {
68 checkAuthClients = configRelay.getValueAsBoolean();
69 }
70 }
71
72 /***
73 * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
74 */
75 public void service(ServiceManager serviceMan) throws ServiceException {
76 dnsServer = (DNSServer) serviceMan.lookup(DNSServer.ROLE);
77 }
78
79 /***
80 * handles MAIL command
81 *
82 * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
83 */
84 public void onCommand(SMTPSession session) {
85 doMAIL(session, session.getCommandArgument());
86 }
87
88
89 /***
90 * Handler method called upon receipt of a MAIL command.
91 * Sets up handler to deliver mail as the stated sender.
92 *
93 * @param session SMTP session object
94 * @param argument the argument passed in with the command by the SMTP client
95 */
96 private void doMAIL(SMTPSession session, String argument) {
97 String responseString = null;
98 StringBuffer responseBuffer = session.getResponseBuffer();
99 String sender = null;
100 boolean badSenderDomain = false;
101
102 if ((argument != null) && (argument.indexOf(":") > 0)) {
103 int colonIndex = argument.indexOf(":");
104 sender = argument.substring(colonIndex + 1);
105 argument = argument.substring(0, colonIndex);
106 }
107 if (session.getState().containsKey(SMTPSession.SENDER)) {
108 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Sender already specified";
109 session.writeResponse(responseString);
110 } else if (!session.getState().containsKey(SMTPSession.CURRENT_HELO_MODE) && session.useHeloEhloEnforcement()) {
111 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need HELO or EHLO before MAIL";
112 session.writeResponse(responseString);
113 } else if (argument == null || !argument.toUpperCase(Locale.US).equals("FROM")
114 || sender == null) {
115 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Usage: MAIL FROM:<sender>";
116 session.writeResponse(responseString);
117 } else {
118 sender = sender.trim();
119
120 int lastChar = sender.indexOf('>', sender.indexOf('<'));
121
122
123 if ((lastChar > 0) && (sender.length() > lastChar + 2) && (sender.charAt(lastChar + 1) == ' ')) {
124 String mailOptionString = sender.substring(lastChar + 2);
125
126
127 sender = sender.substring(0, lastChar + 1);
128
129 StringTokenizer optionTokenizer = new StringTokenizer(mailOptionString, " ");
130 while (optionTokenizer.hasMoreElements()) {
131 String mailOption = optionTokenizer.nextToken();
132 int equalIndex = mailOption.indexOf('=');
133 String mailOptionName = mailOption;
134 String mailOptionValue = "";
135 if (equalIndex > 0) {
136 mailOptionName = mailOption.substring(0, equalIndex).toUpperCase(Locale.US);
137 mailOptionValue = mailOption.substring(equalIndex + 1);
138 }
139
140
141
142 if (mailOptionName.startsWith(MAIL_OPTION_SIZE)) {
143 if (!(doMailSize(session, mailOptionValue, sender))) {
144 return;
145 }
146 } else {
147
148 if (getLogger().isDebugEnabled()) {
149 StringBuffer debugBuffer =
150 new StringBuffer(128)
151 .append("MAIL command had unrecognized/unexpected option ")
152 .append(mailOptionName)
153 .append(" with value ")
154 .append(mailOptionValue);
155 getLogger().debug(debugBuffer.toString());
156 }
157 }
158 }
159 }
160 if (!sender.startsWith("<") || !sender.endsWith(">")) {
161 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in MAIL command";
162 session.writeResponse(responseString);
163 if (getLogger().isErrorEnabled()) {
164 StringBuffer errorBuffer =
165 new StringBuffer(128)
166 .append("Error parsing sender address: ")
167 .append(sender)
168 .append(": did not start and end with < >");
169 getLogger().error(errorBuffer.toString());
170 }
171 return;
172 }
173 MailAddress senderAddress = null;
174
175 sender = sender.substring(1, sender.length() - 1);
176 if (sender.length() == 0) {
177
178 } else {
179
180 if (sender.indexOf("@") < 0) {
181 sender = sender + "@localhost";
182 }
183
184 try {
185 senderAddress = new MailAddress(sender);
186 } catch (Exception pe) {
187 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in sender address";
188 session.writeResponse(responseString);
189 if (getLogger().isErrorEnabled()) {
190 StringBuffer errorBuffer =
191 new StringBuffer(256)
192 .append("Error parsing sender address: ")
193 .append(sender)
194 .append(": ")
195 .append(pe.getMessage());
196 getLogger().error(errorBuffer.toString());
197 }
198 return;
199 }
200 }
201
202
203 if (checkValidSenderDomain == true && senderAddress != null) {
204
205 /***
206 * don't check if the ip address is allowed to relay. Only check if it is set in the config.
207 */
208 if (checkAuthClients || !session.isRelayingAllowed()) {
209
210
211 Collection records;
212
213 records = dnsServer.findMXRecords(senderAddress.getHost());
214 if (records == null || records.size() == 0) {
215 badSenderDomain = true;
216 }
217
218
219 if (badSenderDomain) {
220 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+ " sender " + senderAddress + " contains a domain with no valid MX records";
221 session.writeResponse(responseString);
222 getLogger().info(responseString);
223 }
224 }
225 }
226
227 if (!badSenderDomain) {
228 session.getState().put(SMTPSession.SENDER, senderAddress);
229 responseBuffer.append("250 "+DSNStatus.getStatus(DSNStatus.SUCCESS,DSNStatus.ADDRESS_OTHER)+" Sender <")
230 .append(sender)
231 .append("> OK");
232 responseString = session.clearResponseBuffer();
233 session.writeResponse(responseString);
234 }
235 }
236 }
237
238 /***
239 * Handles the SIZE MAIL option.
240 *
241 * @param session SMTP session object
242 * @param mailOptionValue the option string passed in with the SIZE option
243 * @param tempSender the sender specified in this mail command (for logging purpose)
244 * @return true if further options should be processed, false otherwise
245 */
246 private boolean doMailSize(SMTPSession session, String mailOptionValue, String tempSender) {
247 int size = 0;
248 try {
249 size = Integer.parseInt(mailOptionValue);
250 } catch (NumberFormatException pe) {
251
252 String responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Syntactically incorrect value for SIZE parameter";
253 session.writeResponse(responseString);
254 getLogger().error("Rejected syntactically incorrect value for SIZE parameter.");
255 return false;
256 }
257 if (getLogger().isDebugEnabled()) {
258 StringBuffer debugBuffer =
259 new StringBuffer(128)
260 .append("MAIL command option SIZE received with value ")
261 .append(size)
262 .append(".");
263 getLogger().debug(debugBuffer.toString());
264 }
265 long maxMessageSize = session.getConfigurationData().getMaxMessageSize();
266 if ((maxMessageSize > 0) && (size > maxMessageSize)) {
267
268 String responseString = "552 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SYSTEM_MSG_TOO_BIG)+" Message size exceeds fixed maximum message size";
269 session.writeResponse(responseString);
270 StringBuffer errorBuffer =
271 new StringBuffer(256)
272 .append("Rejected message from ")
273 .append(tempSender != null ? tempSender : null)
274 .append(" from host ")
275 .append(session.getRemoteHost())
276 .append(" (")
277 .append(session.getRemoteIPAddress())
278 .append(") of size ")
279 .append(size)
280 .append(" exceeding system maximum message size of ")
281 .append(maxMessageSize)
282 .append("based on SIZE option.");
283 getLogger().error(errorBuffer.toString());
284 return false;
285 } else {
286
287
288 session.getState().put(MESG_SIZE, new Integer(size));
289 }
290 return true;
291 }
292
293
294 }