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.fastfail;
021    
022    import java.util.Iterator;
023    
024    import org.apache.james.protocols.smtp.SMTPRetCode;
025    import org.apache.james.protocols.smtp.SMTPSession;
026    import org.apache.james.protocols.smtp.dsn.DSNStatus;
027    import org.apache.james.protocols.smtp.hook.HookResult;
028    import org.apache.james.protocols.smtp.hook.HookReturnCode;
029    import org.apache.james.protocols.smtp.hook.RcptHook;
030    import org.apache.mailet.MailAddress;
031    
032    
033    /**
034     * Abstract base class which implement GreyListing. 
035     * 
036     *
037     */
038    public abstract class AbstractGreylistHandler implements RcptHook {
039    
040        /** 1 hour */
041        private long tempBlockTime = 3600000;
042    
043        /** 36 days */
044        private long autoWhiteListLifeTime = 3110400000L;
045    
046        /** 4 hours */
047        private long unseenLifeTime = 14400000;
048    
049    
050        
051        public void setUnseenLifeTime(long unseenLifeTime) {
052            this.unseenLifeTime = unseenLifeTime;
053        }
054        
055        public void setAutoWhiteListLifeTime(long autoWhiteListLifeTime) {
056            this.autoWhiteListLifeTime = autoWhiteListLifeTime;
057        }
058        
059        public void setTempBlockTime(long tempBlockTime) {
060            this.tempBlockTime = tempBlockTime;
061        }
062    
063    
064        private HookResult doGreyListCheck(SMTPSession session, MailAddress senderAddress, MailAddress recipAddress) {
065            String recip = "";
066            String sender = "";
067    
068            if (recipAddress != null) recip = recipAddress.toString();
069            if (senderAddress != null) sender = senderAddress.toString();
070        
071            long time = System.currentTimeMillis();
072            String ipAddress = session.getRemoteIPAddress();
073            
074            try {
075                long createTimeStamp = 0;
076                int count = 0;
077                
078                // get the timestamp when he triplet was last seen
079                Iterator<String> data = getGreyListData(ipAddress, sender, recip);
080                
081                if (data.hasNext()) {
082                    createTimeStamp = Long.parseLong(data.next());
083                    count = Integer.parseInt(data.next());
084                }
085                
086                session.getLogger().debug("Triplet " + ipAddress + " | " + sender + " | " + recip  +" -> TimeStamp: " + createTimeStamp);
087    
088    
089                // if the timestamp is bigger as 0 we have allready a triplet stored
090                if (createTimeStamp > 0) {
091                    long acceptTime = createTimeStamp + tempBlockTime;
092            
093                    if ((time < acceptTime) && (count == 0)) {
094                        return new HookResult(HookReturnCode.DENYSOFT, SMTPRetCode.LOCAL_ERROR, DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER) 
095                            + " Temporary rejected: Reconnect to fast. Please try again later");
096                    } else {
097                        
098                        session.getLogger().debug("Update triplet " + ipAddress + " | " + sender + " | " + recip + " -> timestamp: " + time);
099                        
100                        // update the triplet..
101                        updateTriplet(ipAddress, sender, recip, count, time);
102    
103                    }
104                } else {
105                    session.getLogger().debug("New triplet " + ipAddress + " | " + sender + " | " + recip );
106               
107                    // insert a new triplet
108                    insertTriplet(ipAddress, sender, recip, count, time);
109          
110                    // Tempory block on new triplet!
111                    return new HookResult(HookReturnCode.DENYSOFT, SMTPRetCode.LOCAL_ERROR, DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER) 
112                        + " Temporary rejected: Please try again later");
113                }
114    
115                // some kind of random cleanup process
116                if (Math.random() > 0.99) {
117                    // cleanup old entries
118                
119                    session.getLogger().debug("Delete old entries");
120                
121                    cleanupAutoWhiteListGreyList(time - autoWhiteListLifeTime);
122                    cleanupGreyList(time - unseenLifeTime);
123                }
124    
125            } catch (Exception e) {
126                // just log the exception
127                session.getLogger().error("Error on greylist method: " + e.getMessage());
128            }
129            return new HookResult(HookReturnCode.DECLINED);
130        }
131    
132        /**
133         * Get all necessary data for greylisting based on provided triplet
134         * 
135         * @param ipAddress
136         *            The ipAddress of the client
137         * @param sender
138         *            The mailFrom
139         * @param recip
140         *            The rcptTo
141         * @return data
142         *            The data
143         * @throws Exception
144         */
145        protected abstract  Iterator<String> getGreyListData(String ipAddress, String sender, String recip) throws Exception;
146    
147        /**
148         * Insert new triplet in the store
149         * 
150         * @param ipAddress
151         *            The ipAddress of the client
152         * @param sender
153         *            The mailFrom
154         * @param recip
155         *            The rcptTo
156         * @param count
157         *            The count
158         * @param createTime
159         *            The createTime
160         * @throws SQLException
161         */
162        protected abstract void insertTriplet(String ipAddress, String sender, String recip, int count, long createTime)
163            throws Exception;
164    
165        /**
166         * Update the triplet
167         * 
168         * 
169         * @param ipAddress
170         *            The ipAddress of the client
171         * @param sender
172         *            The mailFrom
173         * @param recip
174         *            The rcptTo
175         * @param count
176         *            The count
177         * @param time
178         *            the current time in ms
179         * @throws Exception
180         */
181        protected abstract void updateTriplet(String ipAddress, String sender, String recip, int count, long time) throws Exception;
182           
183    
184        /**
185         * Cleanup the autowhitelist
186         * 
187         * @param time
188         *            The time which must be reached before delete the records
189         * @throws Exception
190         */
191        protected abstract void cleanupAutoWhiteListGreyList(long time)throws Exception;     
192    
193        /**
194         * Delete old entries from the Greylist datarecord 
195         * 
196         * @param time
197         *            The time which must be reached before delete the records
198         * @throws Exception
199         */
200        protected abstract void cleanupGreyList(long time) throws Exception;
201    
202      
203    
204        /**
205         * @see org.apache.james.protocols.smtp.hook.RcptHook#doRcpt(org.apache.james.protocols.smtp.SMTPSession, org.apache.mailet.MailAddress, org.apache.mailet.MailAddress)
206         */
207        public HookResult doRcpt(SMTPSession session, MailAddress sender, MailAddress rcpt) {
208            if (!session.isRelayingAllowed()) {
209                return doGreyListCheck(session, sender,rcpt);
210            } else {
211                session.getLogger().info("IpAddress " + session.getRemoteIPAddress() + " is allowed to send. Skip greylisting.");
212            }
213            return new HookResult(HookReturnCode.DECLINED);
214        }
215    }