001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019
020 package org.apache.james.protocols.smtp.core;
021
022 import java.util.ArrayList;
023 import java.util.Collection;
024 import java.util.HashMap;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.Locale;
028 import java.util.Map;
029 import java.util.StringTokenizer;
030
031 import org.apache.james.protocols.api.Request;
032 import org.apache.james.protocols.api.Response;
033 import org.apache.james.protocols.smtp.SMTPResponse;
034 import org.apache.james.protocols.smtp.SMTPRetCode;
035 import org.apache.james.protocols.smtp.SMTPSession;
036 import org.apache.james.protocols.smtp.dsn.DSNStatus;
037 import org.apache.james.protocols.smtp.hook.HookResult;
038 import org.apache.james.protocols.smtp.hook.MailHook;
039 import org.apache.james.protocols.smtp.hook.MailParametersHook;
040 import org.apache.mailet.MailAddress;
041
042 /**
043 * Handles MAIL command
044 */
045 public class MailCmdHandler extends AbstractHookableCmdHandler<MailHook> {
046
047 /**
048 * A map of parameterHooks
049 */
050 private Map<String, MailParametersHook> paramHooks;
051
052 /**
053 * @see
054 * org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler
055 * #onCommand(SMTPSession, Request)
056 */
057 public Response onCommand(SMTPSession session, Request request) {
058 Response response = super.onCommand(session, request);
059 // Check if the response was not ok
060 if (response.getRetCode().equals(SMTPRetCode.MAIL_OK) == false) {
061 // cleanup the session
062 session.getState().remove(SMTPSession.SENDER);
063 }
064
065 return response;
066 }
067
068 /**
069 * Handler method called upon receipt of a MAIL command. Sets up handler to
070 * deliver mail as the stated sender.
071 *
072 * @param session
073 * SMTP session object
074 * @param argument
075 * the argument passed in with the command by the SMTP client
076 */
077 private SMTPResponse doMAIL(SMTPSession session, String argument) {
078 StringBuilder responseBuffer = new StringBuilder();
079 MailAddress sender = (MailAddress) session.getState().get(
080 SMTPSession.SENDER);
081 responseBuffer.append(
082 DSNStatus.getStatus(DSNStatus.SUCCESS, DSNStatus.ADDRESS_OTHER))
083 .append(" Sender <");
084 if (sender != null) {
085 responseBuffer.append(sender);
086 }
087 responseBuffer.append("> OK");
088 return new SMTPResponse(SMTPRetCode.MAIL_OK, responseBuffer);
089 }
090
091 /**
092 * @see org.apache.james.protocols.api.handler.CommandHandler#getImplCommands()
093 */
094 public Collection<String> getImplCommands() {
095 Collection<String> implCommands = new ArrayList<String>();
096 implCommands.add("MAIL");
097
098 return implCommands;
099 }
100
101 /**
102 * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#doCoreCmd(org.apache.james.protocols.smtp.SMTPSession,
103 * java.lang.String, java.lang.String)
104 */
105 protected SMTPResponse doCoreCmd(SMTPSession session, String command,
106 String parameters) {
107 return doMAIL(session, parameters);
108 }
109
110 /**
111 * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#doFilterChecks(org.apache.james.protocols.smtp.SMTPSession,
112 * java.lang.String, java.lang.String)
113 */
114 protected SMTPResponse doFilterChecks(SMTPSession session, String command,
115 String parameters) {
116 return doMAILFilter(session, parameters);
117 }
118
119 /**
120 * @param session
121 * SMTP session object
122 * @param argument
123 * the argument passed in with the command by the SMTP client
124 */
125 private SMTPResponse doMAILFilter(SMTPSession session, String argument) {
126 String sender = null;
127
128 if ((argument != null) && (argument.indexOf(":") > 0)) {
129 int colonIndex = argument.indexOf(":");
130 sender = argument.substring(colonIndex + 1);
131 argument = argument.substring(0, colonIndex);
132 }
133 if (session.getState().containsKey(SMTPSession.SENDER)) {
134 return new SMTPResponse(SMTPRetCode.BAD_SEQUENCE, DSNStatus
135 .getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER)
136 + " Sender already specified");
137 } else if (!session.getConnectionState().containsKey(
138 SMTPSession.CURRENT_HELO_MODE)
139 && session.useHeloEhloEnforcement()) {
140 return new SMTPResponse(SMTPRetCode.BAD_SEQUENCE, DSNStatus
141 .getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER)
142 + " Need HELO or EHLO before MAIL");
143 } else if (argument == null
144 || !argument.toUpperCase(Locale.US).equals("FROM")
145 || sender == null) {
146 return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
147 DSNStatus.getStatus(DSNStatus.PERMANENT,
148 DSNStatus.DELIVERY_INVALID_ARG)
149 + " Usage: MAIL FROM:<sender>");
150 } else {
151 sender = sender.trim();
152 // the next gt after the first lt ... AUTH may add more <>
153 int lastChar = sender.indexOf('>', sender.indexOf('<'));
154 // Check to see if any options are present and, if so, whether they
155 // are correctly formatted
156 // (separated from the closing angle bracket by a ' ').
157 if ((lastChar > 0) && (sender.length() > lastChar + 2)
158 && (sender.charAt(lastChar + 1) == ' ')) {
159 String mailOptionString = sender.substring(lastChar + 2);
160
161 // Remove the options from the sender
162 sender = sender.substring(0, lastChar + 1);
163
164 StringTokenizer optionTokenizer = new StringTokenizer(
165 mailOptionString, " ");
166 while (optionTokenizer.hasMoreElements()) {
167 String mailOption = optionTokenizer.nextToken();
168 int equalIndex = mailOption.indexOf('=');
169 String mailOptionName = mailOption;
170 String mailOptionValue = "";
171 if (equalIndex > 0) {
172 mailOptionName = mailOption.substring(0, equalIndex)
173 .toUpperCase(Locale.US);
174 mailOptionValue = mailOption.substring(equalIndex + 1);
175 }
176
177 // Handle the SIZE extension keyword
178
179 if (paramHooks.containsKey(mailOptionName)) {
180 MailParametersHook hook = paramHooks.get(mailOptionName);
181 SMTPResponse res = calcDefaultSMTPResponse(hook.doMailParameter(session, mailOptionName, mailOptionValue));
182 if (res != null) {
183 return res;
184 }
185 } else {
186 // Unexpected option attached to the Mail command
187 if (session.getLogger().isDebugEnabled()) {
188 StringBuilder debugBuffer = new StringBuilder(128)
189 .append(
190 "MAIL command had unrecognized/unexpected option ")
191 .append(mailOptionName).append(
192 " with value ").append(
193 mailOptionValue);
194 session.getLogger().debug(debugBuffer.toString());
195 }
196 }
197 }
198 }
199 if (session.useAddressBracketsEnforcement()
200 && (!sender.startsWith("<") || !sender.endsWith(">"))) {
201 if (session.getLogger().isInfoEnabled()) {
202 StringBuilder errorBuffer = new StringBuilder(128).append(
203 "Error parsing sender address: ").append(sender)
204 .append(": did not start and end with < >");
205 session.getLogger().info(errorBuffer.toString());
206 }
207 return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
208 DSNStatus.getStatus(DSNStatus.PERMANENT,
209 DSNStatus.ADDRESS_SYNTAX_SENDER)
210 + " Syntax error in MAIL command");
211 }
212 MailAddress senderAddress = null;
213
214 if (session.useAddressBracketsEnforcement()
215 || (sender.startsWith("<") && sender.endsWith(">"))) {
216 // Remove < and >
217 sender = sender.substring(1, sender.length() - 1);
218 }
219
220 if (sender.length() == 0) {
221 // This is the <> case. Let senderAddress == null
222 } else {
223
224 if (sender.indexOf("@") < 0) {
225 sender = sender
226 + "@"
227 + getDefaultDomain();
228 }
229
230 try {
231 senderAddress = new MailAddress(sender);
232 } catch (Exception pe) {
233 if (session.getLogger().isInfoEnabled()) {
234 StringBuilder errorBuffer = new StringBuilder(256)
235 .append("Error parsing sender address: ")
236 .append(sender).append(": ").append(
237 pe.getMessage());
238 session.getLogger().info(errorBuffer.toString());
239 }
240 return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
241 DSNStatus.getStatus(DSNStatus.PERMANENT,
242 DSNStatus.ADDRESS_SYNTAX_SENDER)
243 + " Syntax error in sender address");
244 }
245 }
246
247 // Store the senderAddress in session map
248 session.getState().put(SMTPSession.SENDER, senderAddress);
249 }
250 return null;
251 }
252 /**
253 * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#getHookInterface()
254 */
255 protected Class<MailHook> getHookInterface() {
256 return MailHook.class;
257 }
258
259
260 /**
261 * {@inheritDoc}
262 */
263 protected HookResult callHook(MailHook rawHook, SMTPSession session, String parameters) {
264 return rawHook.doMail(session,(MailAddress) session.getState().get(SMTPSession.SENDER));
265 }
266
267
268 /**
269 * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#getMarkerInterfaces()
270 */
271 public List<Class<?>> getMarkerInterfaces() {
272 List<Class<?>> l = super.getMarkerInterfaces();
273 l.add(MailParametersHook.class);
274 return l;
275 }
276
277 /**
278 * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#wireExtensions(java.lang.Class, java.util.List)
279 */
280 @SuppressWarnings("unchecked")
281 public void wireExtensions(Class interfaceName, List extension) {
282 if (MailParametersHook.class.equals(interfaceName)) {
283 this.paramHooks = new HashMap<String, MailParametersHook>();
284 for (Iterator<MailParametersHook> i = extension.iterator(); i.hasNext(); ) {
285 MailParametersHook hook = i.next();
286 String[] params = hook.getMailParamNames();
287 for (int k = 0; k < params.length; k++) {
288 paramHooks.put(params[k], hook);
289 }
290 }
291 } else {
292 super.wireExtensions(interfaceName, extension);
293 }
294 }
295
296 /**
297 * Return the default domain to append if the sender contains none
298 *
299 * @return defaultDomain
300 */
301 protected String getDefaultDomain() {
302 return "localhost";
303 }
304
305 }