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.esmtp;
021    
022    import java.util.LinkedList;
023    import java.util.List;
024    
025    import org.apache.james.protocols.api.Response;
026    import org.apache.james.protocols.api.handler.LineHandler;
027    import org.apache.james.protocols.smtp.MailEnvelope;
028    import org.apache.james.protocols.smtp.SMTPRetCode;
029    import org.apache.james.protocols.smtp.SMTPSession;
030    import org.apache.james.protocols.smtp.core.DataLineFilter;
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.HookReturnCode;
034    import org.apache.james.protocols.smtp.hook.MailParametersHook;
035    import org.apache.james.protocols.smtp.hook.MessageHook;
036    
037    /**
038     * Handle the ESMTP SIZE extension.
039     */
040    public class MailSizeEsmtpExtension implements MailParametersHook, EhloExtension, DataLineFilter, MessageHook {
041    
042        private final static String MESG_SIZE = "MESG_SIZE"; // The size of the
043        private final static String MESG_FAILED = "MESG_FAILED";   // Message failed flag
044    
045    
046        /**
047         * @see org.apache.james.protocols.smtp.hook.MailParametersHook#doMailParameter(org.apache.james.protocols.smtp.SMTPSession, java.lang.String, java.lang.String)
048         */
049        public HookResult doMailParameter(SMTPSession session, String paramName,
050                String paramValue) {
051            HookResult res = doMailSize(session, paramValue,
052                    (String) session.getState().get(SMTPSession.SENDER));
053            return res;
054        }
055    
056        /**
057         * @see org.apache.james.protocols.smtp.hook.MailParametersHook#getMailParamNames()
058         */
059        public String[] getMailParamNames() {
060            return new String[] { "SIZE" };
061        }
062    
063        /**
064         * @see org.apache.james.protocols.smtp.core.esmtp.EhloExtension#getImplementedEsmtpFeatures(org.apache.james.protocols.smtp.SMTPSession)
065         */
066        public List<String> getImplementedEsmtpFeatures(SMTPSession session) {
067            LinkedList<String> resp = new LinkedList<String>();
068            // Extension defined in RFC 1870
069            long maxMessageSize = session.getMaxMessageSize();
070            if (maxMessageSize > 0) {
071                resp.add("SIZE " + maxMessageSize);
072            }
073            return resp;
074        }
075    
076    
077        /**
078         * Handles the SIZE MAIL option.
079         * 
080         * @param session
081         *            SMTP session object
082         * @param mailOptionValue
083         *            the option string passed in with the SIZE option
084         * @param tempSender
085         *            the sender specified in this mail command (for logging
086         *            purpose)
087         * @return true if further options should be processed, false otherwise
088         */
089        private HookResult doMailSize(SMTPSession session,
090                String mailOptionValue, String tempSender) {
091            int size = 0;
092            try {
093                size = Integer.parseInt(mailOptionValue);
094            } catch (NumberFormatException pe) {
095                session.getLogger().error("Rejected syntactically incorrect value for SIZE parameter.");
096                
097                // This is a malformed option value. We return an error
098                return new HookResult(HookReturnCode.DENY, 
099                        SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
100                        DSNStatus.getStatus(DSNStatus.PERMANENT,
101                                DSNStatus.DELIVERY_INVALID_ARG)
102                                + " Syntactically incorrect value for SIZE parameter");
103            }
104            if (session.getLogger().isDebugEnabled()) {
105                StringBuilder debugBuffer = new StringBuilder(128).append(
106                        "MAIL command option SIZE received with value ").append(
107                        size).append(".");
108                session.getLogger().debug(debugBuffer.toString());
109            }
110            long maxMessageSize = session.getMaxMessageSize();
111            if ((maxMessageSize > 0) && (size > maxMessageSize)) {
112                // Let the client know that the size limit has been hit.
113                StringBuilder errorBuffer = new StringBuilder(256).append(
114                        "Rejected message from ").append(
115                        tempSender != null ? tempSender : null).append(
116                        " from host ").append(session.getRemoteHost()).append(" (")
117                        .append(session.getRemoteIPAddress()).append(") of size ")
118                        .append(size).append(
119                                " exceeding system maximum message size of ")
120                        .append(maxMessageSize).append("based on SIZE option.");
121                session.getLogger().error(errorBuffer.toString());
122    
123                return new HookResult(HookReturnCode.DENY, SMTPRetCode.QUOTA_EXCEEDED, DSNStatus
124                        .getStatus(DSNStatus.PERMANENT,
125                                DSNStatus.SYSTEM_MSG_TOO_BIG)
126                        + " Message size exceeds fixed maximum message size");
127            } else {
128                // put the message size in the message state so it can be used
129                // later to restrict messages for user quotas, etc.
130                session.getState().put(MESG_SIZE, Integer.valueOf(size));
131            }
132            return null;
133        }
134    
135    
136        /**
137         * @see org.apache.james.protocols.smtp.core.DataLineFilter#onLine(SMTPSession, byte[], LineHandler)
138         */
139        public Response onLine(SMTPSession session, byte[] line, LineHandler<SMTPSession> next) {
140            Response response = null;
141            Boolean failed = (Boolean) session.getState().get(MESG_FAILED);
142            // If we already defined we failed and sent a reply we should simply
143            // wait for a CRLF.CRLF to be sent by the client.
144            if (failed != null && failed.booleanValue()) {
145                // TODO
146            } else {
147                if (line.length == 3 && line[0] == 46) {
148                    response = next.onLine(session, line);
149                } else {
150                    Long currentSize = (Long) session.getState().get("CURRENT_SIZE");
151                    Long newSize;
152                    if (currentSize == null) {
153                        newSize = Long.valueOf(line.length);
154                    } else {
155                        newSize = Long.valueOf(currentSize.intValue()+line.length);
156                    }
157                    
158                    if (session.getMaxMessageSize() > 0 && newSize.intValue() > session.getMaxMessageSize()) {
159                        // Add an item to the state to suppress
160                        // logging of extra lines of data
161                        // that are sent after the size limit has
162                        // been hit.
163                        session.getState().put(MESG_FAILED, Boolean.TRUE);
164                        // then let the client know that the size
165                        // limit has been hit.
166                        response = next.onLine(session, ".\r\n".getBytes());
167                    } else {
168                        response = next.onLine(session, line);
169                    }
170                    
171                    session.getState().put("CURRENT_SIZE", newSize);
172                }
173            }
174            return response;
175        }
176    
177        /**
178         * @see org.apache.james.protocols.smtp.hook.MessageHook#onMessage(SMTPSession, MailEnvelope)
179         */
180        public HookResult onMessage(SMTPSession session, MailEnvelope mail) {
181            Boolean failed = (Boolean) session.getState().get(MESG_FAILED);
182            if (failed != null && failed.booleanValue()) {
183                HookResult response = new HookResult(HookReturnCode.DENY, SMTPRetCode.QUOTA_EXCEEDED,DSNStatus.getStatus(DSNStatus.PERMANENT,
184                        DSNStatus.SYSTEM_MSG_TOO_BIG) + " Maximum message size exceeded");
185      
186                StringBuilder errorBuffer = new StringBuilder(256).append(
187                        "Rejected message from ").append(
188                        session.getState().get(SMTPSession.SENDER).toString())
189                        .append(" from host ").append(session.getRemoteHost())
190                        .append(" (").append(session.getRemoteIPAddress())
191                        .append(") exceeding system maximum message size of ")
192                        .append(
193                                session.getMaxMessageSize());
194                session.getLogger().error(errorBuffer.toString());
195                return response;
196            } else {
197                return new HookResult(HookReturnCode.DECLINED);
198            }
199        }
200    
201    }