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    }