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