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.Locale;
025    import java.util.StringTokenizer;
026    
027    import org.apache.james.protocols.api.handler.CommandHandler;
028    import org.apache.james.protocols.smtp.SMTPResponse;
029    import org.apache.james.protocols.smtp.SMTPRetCode;
030    import org.apache.james.protocols.smtp.SMTPSession;
031    import org.apache.james.protocols.smtp.dsn.DSNStatus;
032    import org.apache.james.protocols.smtp.hook.HookResult;
033    import org.apache.james.protocols.smtp.hook.RcptHook;
034    import org.apache.mailet.MailAddress;
035    
036    /**
037     * Handles RCPT command
038     */
039    public class RcptCmdHandler extends AbstractHookableCmdHandler<RcptHook> implements
040            CommandHandler<SMTPSession> {
041    
042        public static final String CURRENT_RECIPIENT = "CURRENT_RECIPIENT"; // Current recipient
043    
044       
045        
046        /**
047         * Handler method called upon receipt of a RCPT command. Reads recipient.
048         * Does some connection validation.
049         * 
050         * 
051         * @param session
052         *            SMTP session object
053         * @param command
054         *            command passed
055         * @param parameters
056         *            parameters passed in with the command by the SMTP client
057         */
058        @SuppressWarnings("unchecked")
059        protected SMTPResponse doCoreCmd(SMTPSession session, String command,
060                String parameters) {
061            Collection<MailAddress> rcptColl = (Collection<MailAddress>) session.getState().get(
062                    SMTPSession.RCPT_LIST);
063            if (rcptColl == null) {
064                rcptColl = new ArrayList<MailAddress>();
065            }
066            MailAddress recipientAddress = (MailAddress) session.getState().get(
067                    CURRENT_RECIPIENT);
068            rcptColl.add(recipientAddress);
069            session.getState().put(SMTPSession.RCPT_LIST, rcptColl);
070            StringBuilder response = new StringBuilder();
071            response
072                    .append(
073                            DSNStatus.getStatus(DSNStatus.SUCCESS,
074                                    DSNStatus.ADDRESS_VALID))
075                    .append(" Recipient <").append(recipientAddress).append("> OK");
076            return new SMTPResponse(SMTPRetCode.MAIL_OK, response);
077    
078        }
079    
080        /**
081         * @param session
082         *            SMTP session object
083         * @param argument
084         *            the argument passed in with the command by the SMTP client
085         */
086        protected SMTPResponse doFilterChecks(SMTPSession session, String command,
087                String argument) {
088            String recipient = null;
089            if ((argument != null) && (argument.indexOf(":") > 0)) {
090                int colonIndex = argument.indexOf(":");
091                recipient = argument.substring(colonIndex + 1);
092                argument = argument.substring(0, colonIndex);
093            }
094            if (!session.getState().containsKey(SMTPSession.SENDER)) {
095                return new SMTPResponse(SMTPRetCode.BAD_SEQUENCE, DSNStatus
096                        .getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER)
097                        + " Need MAIL before RCPT");
098            } else if (argument == null
099                    || !argument.toUpperCase(Locale.US).equals("TO")
100                    || recipient == null) {
101                return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
102                        DSNStatus.getStatus(DSNStatus.PERMANENT,
103                                DSNStatus.DELIVERY_SYNTAX)
104                                + " Usage: RCPT TO:<recipient>");
105            }
106    
107            recipient = recipient.trim();
108            int lastChar = recipient.lastIndexOf('>');
109            // Check to see if any options are present and, if so, whether they
110            // are correctly formatted
111            // (separated from the closing angle bracket by a ' ').
112            String rcptOptionString = null;
113            if ((lastChar > 0) && (recipient.length() > lastChar + 2)
114                    && (recipient.charAt(lastChar + 1) == ' ')) {
115                rcptOptionString = recipient.substring(lastChar + 2);
116    
117                // Remove the options from the recipient
118                recipient = recipient.substring(0, lastChar + 1);
119            }
120            if (session.useAddressBracketsEnforcement()
121                    && (!recipient.startsWith("<") || !recipient.endsWith(">"))) {
122                if (session.getLogger().isInfoEnabled()) {
123                    StringBuilder errorBuffer = new StringBuilder(192).append(
124                            "Error parsing recipient address: ").append(
125                            "Address did not start and end with < >").append(
126                            getContext(session, null, recipient));
127                    session.getLogger().info(errorBuffer.toString());
128                }
129                return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
130                        DSNStatus.getStatus(DSNStatus.PERMANENT,
131                                DSNStatus.DELIVERY_SYNTAX)
132                                + " Syntax error in parameters or arguments");
133            }
134            MailAddress recipientAddress = null;
135            // Remove < and >
136            if (session.useAddressBracketsEnforcement()
137                    || (recipient.startsWith("<") && recipient.endsWith(">"))) {
138                recipient = recipient.substring(1, recipient.length() - 1);
139            }
140    
141            if (recipient.indexOf("@") < 0) {
142                // set the default domain
143                recipient = recipient
144                        + "@"
145                        + getDefaultDomain();
146            }
147    
148            try {
149                recipientAddress = new MailAddress(recipient);
150            } catch (Exception pe) {
151                if (session.getLogger().isInfoEnabled()) {
152                    StringBuilder errorBuffer = new StringBuilder(192).append(
153                            "Error parsing recipient address: ").append(
154                            getContext(session, recipientAddress, recipient))
155                            .append(pe.getMessage());
156                    session.getLogger().info(errorBuffer.toString());
157                }
158                /*
159                 * from RFC2822; 553 Requested action not taken: mailbox name
160                 * not allowed (e.g., mailbox syntax incorrect)
161                 */
162                return new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_MAILBOX,
163                        DSNStatus.getStatus(DSNStatus.PERMANENT,
164                                DSNStatus.ADDRESS_SYNTAX)
165                                + " Syntax error in recipient address");
166            }
167    
168            if (rcptOptionString != null) {
169    
170                StringTokenizer optionTokenizer = new StringTokenizer(
171                        rcptOptionString, " ");
172                while (optionTokenizer.hasMoreElements()) {
173                    String rcptOption = optionTokenizer.nextToken();
174                    int equalIndex = rcptOption.indexOf('=');
175                    String rcptOptionName = rcptOption;
176                    String rcptOptionValue = "";
177                    if (equalIndex > 0) {
178                        rcptOptionName = rcptOption.substring(0, equalIndex)
179                                .toUpperCase(Locale.US);
180                        rcptOptionValue = rcptOption.substring(equalIndex + 1);
181                    }
182                    // Unexpected option attached to the RCPT command
183                    if (session.getLogger().isDebugEnabled()) {
184                        StringBuilder debugBuffer = new StringBuilder(128)
185                                .append(
186                                        "RCPT command had unrecognized/unexpected option ")
187                                .append(rcptOptionName).append(" with value ")
188                                .append(rcptOptionValue).append(
189                                        getContext(session, recipientAddress,
190                                                recipient));
191                        session.getLogger().debug(debugBuffer.toString());
192                    }
193    
194                    return new SMTPResponse(
195                            SMTPRetCode.PARAMETER_NOT_IMPLEMENTED,
196                            "Unrecognized or unsupported option: "
197                                    + rcptOptionName);
198                }
199                optionTokenizer = null;
200            }
201    
202            session.getState().put(CURRENT_RECIPIENT,recipientAddress);
203    
204            return null;
205        }
206    
207        private String getContext(SMTPSession session,
208                MailAddress recipientAddress, String recipient) {
209            StringBuilder sb = new StringBuilder(128);
210            if (null != recipientAddress) {
211                sb
212                        .append(" [to:"
213                                + (recipientAddress).toInternetAddress()
214                                        .getAddress() + "]");
215            } else if (null != recipient) {
216                sb.append(" [to:" + recipient + "]");
217            }
218            if (null != session.getState().get(SMTPSession.SENDER)) {
219                sb
220                        .append(" [from:"
221                                + ((MailAddress) session.getState().get(
222                                        SMTPSession.SENDER)).toInternetAddress()
223                                        .getAddress() + "]");
224            }
225            return sb.toString();
226        }
227    
228        /**
229         * @see org.apache.james.protocols.api.handler.CommandHandler#getImplCommands()
230         */
231        public Collection<String> getImplCommands() {
232            Collection<String> implCommands = new ArrayList<String>();
233            implCommands.add("RCPT");
234    
235            return implCommands;
236        }
237    
238        /**
239         * @see org.apache.james.protocols.smtp.core.AbstractHookableCmdHandler#getHookInterface()
240         */
241        protected Class<RcptHook> getHookInterface() {
242            return RcptHook.class;
243        }
244    
245        /**
246         * {@inheritDoc}
247         */
248        protected HookResult callHook(RcptHook rawHook, SMTPSession session,
249                String parameters) {
250            return rawHook.doRcpt(session,
251                    (MailAddress) session.getState().get(SMTPSession.SENDER),
252                    (MailAddress) session.getState().get(CURRENT_RECIPIENT));
253        }
254    
255        protected String getDefaultDomain() {
256            return "localhost";
257        }
258    }