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    
021    
022    package org.apache.james.protocols.smtp.core.fastfail;
023    
024    import java.util.Collection;
025    import java.util.StringTokenizer;
026    
027    import org.apache.james.protocols.api.handler.ConnectHandler;
028    import org.apache.james.protocols.smtp.DNSService;
029    import org.apache.james.protocols.smtp.SMTPSession;
030    import org.apache.james.protocols.smtp.dsn.DSNStatus;
031    import org.apache.james.protocols.smtp.hook.HookResult;
032    import org.apache.james.protocols.smtp.hook.HookReturnCode;
033    import org.apache.james.protocols.smtp.hook.RcptHook;
034    import org.apache.mailet.MailAddress;
035    
036    /**
037      * Connect handler for DNSRBL processing
038      */
039    public class DNSRBLHandler implements  RcptHook{
040    
041        
042        /**
043         * The lists of rbl servers to be checked to limit spam
044         */
045        private String[] whitelist;
046        private String[] blacklist;
047        
048        private DNSService dnsService = null;
049        
050        private boolean getDetail = false;
051        
052        private String blocklistedDetail = null;
053        
054        public static final String RBL_BLOCKLISTED_MAIL_ATTRIBUTE_NAME = "org.apache.james.smtpserver.rbl.blocklisted";
055        
056        public static final String RBL_DETAIL_MAIL_ATTRIBUTE_NAME = "org.apache.james.smtpserver.rbl.detail";
057    
058    
059        /**
060         * Sets the DNS service.
061         * @param dnsService the dnsService to set
062         */
063        public final void setDNSService(DNSService dnsService) {
064            this.dnsService = dnsService;
065        }
066    
067       
068        
069        /**
070         * Set the whitelist array
071         * 
072         * @param whitelist The array which contains the whitelist
073         */
074        public void setWhitelist(String[] whitelist) {
075            // We need to copy the String array because of possible security issues.
076            // Similar to https://issues.apache.org/jira/browse/PROTOCOLS-18
077            if (whitelist != null) {
078                this.whitelist = new String[whitelist.length];
079                for (int i = 0; i < whitelist.length; i++) {
080                    this.whitelist[i] = new String(whitelist[i]);
081                }
082            }
083            this.whitelist = whitelist;
084        }
085        
086        /**
087         * Set the blacklist array
088         * 
089         * @param blacklist The array which contains the blacklist
090         */
091        public void setBlacklist(String[] blacklist) {
092            // We need to copy the String array because of possible security issues.
093            // Similar to https://issues.apache.org/jira/browse/PROTOCOLS-18
094            if (blacklist != null) {
095                this.blacklist = new String[blacklist.length];
096                for (int i = 0; i < blacklist.length; i++) {
097                    this.blacklist[i] = new String(blacklist[i]);
098                }
099            }
100        }
101    
102        /**
103         * Set for try to get a TXT record for the blocked record. 
104         * 
105         * @param getDetail Set to ture for enable
106         */
107        public void setGetDetail(boolean getDetail) {
108            this.getDetail = getDetail;
109        }
110    
111        /**
112         *
113         * This checks DNSRBL whitelists and blacklists.  If the remote IP is whitelisted
114         * it will be permitted to send e-mail, otherwise if the remote IP is blacklisted,
115         * the sender will only be permitted to send e-mail to postmaster (RFC 2821) or
116         * abuse (RFC 2142), unless authenticated.
117         */
118    
119        public void checkDNSRBL(SMTPSession session, String ipAddress) {
120            
121            /*
122             * don't check against rbllists if the client is allowed to relay..
123             * This whould make no sense.
124             */
125            if (session.isRelayingAllowed()) {
126                session.getLogger().info("Ipaddress " + session.getRemoteIPAddress() + " is allowed to relay. Don't check it");
127                return;
128            }
129            
130            if (whitelist != null || blacklist != null) {
131                StringBuffer sb = new StringBuffer();
132                StringTokenizer st = new StringTokenizer(ipAddress, " .", false);
133                while (st.hasMoreTokens()) {
134                    sb.insert(0, st.nextToken() + ".");
135                }
136                String reversedOctets = sb.toString();
137    
138                if (whitelist != null) {
139                    String[] rblList = whitelist;
140                    for (int i = 0 ; i < rblList.length ; i++) try {
141                        dnsService.getByName(reversedOctets + rblList[i]);
142                        if (session.getLogger().isInfoEnabled()) {
143                            session.getLogger().info("Connection from " + ipAddress + " whitelisted by " + rblList[i]);
144                        }
145                        
146                        return;
147                    } catch (java.net.UnknownHostException uhe) {
148                        if (session.getLogger().isDebugEnabled()) {
149                            session.getLogger().debug("IpAddress " + session.getRemoteIPAddress() + " not listed on " + rblList[i]);
150                        }
151                    }
152                }
153    
154                if (blacklist != null) {
155                    String[] rblList = blacklist;
156                    for (int i = 0 ; i < rblList.length ; i++) try {
157                        dnsService.getByName(reversedOctets + rblList[i]);
158                        if (session.getLogger().isInfoEnabled()) {
159                            session.getLogger().info("Connection from " + ipAddress + " restricted by " + rblList[i] + " to SMTP AUTH/postmaster/abuse.");
160                        }
161                        
162                        // we should try to retrieve details
163                        if (getDetail) {
164                            Collection<String> txt = dnsService.findTXTRecords(reversedOctets + rblList[i]);
165                            
166                            // Check if we found a txt record
167                            if (!txt.isEmpty()) {
168                                // Set the detail
169                                String blocklistedDetail = txt.iterator().next().toString();
170                                
171                                session.getConnectionState().put(RBL_DETAIL_MAIL_ATTRIBUTE_NAME, blocklistedDetail);
172                            }
173                        }
174                        
175                        session.getConnectionState().put(RBL_BLOCKLISTED_MAIL_ATTRIBUTE_NAME, "true");
176                        return;
177                    } catch (java.net.UnknownHostException uhe) {
178                        // if it is unknown, it isn't blocked
179                        if (session.getLogger().isDebugEnabled()) {
180                            session.getLogger().debug("unknown host exception thrown:" + rblList[i]);
181                        }
182                    }
183                }
184            }
185        }
186    
187        /**
188         * @see org.apache.james.protocols.smtp.hook.RcptHook#doRcpt(org.apache.james.protocols.smtp.SMTPSession, org.apache.mailet.MailAddress, org.apache.mailet.MailAddress)
189         */
190        public HookResult doRcpt(SMTPSession session, MailAddress sender, MailAddress rcpt) {
191            checkDNSRBL(session, session.getRemoteIPAddress());
192    
193            if (!session.isRelayingAllowed()) {
194                String blocklisted = (String) session.getConnectionState().get(RBL_BLOCKLISTED_MAIL_ATTRIBUTE_NAME);
195        
196                if (blocklisted != null) { // was found in the RBL
197                    if (blocklistedDetail == null) {
198                        return new HookResult(HookReturnCode.DENY,DSNStatus.getStatus(DSNStatus.PERMANENT,
199                                DSNStatus.SECURITY_AUTH)  + " Rejected: unauthenticated e-mail from " + session.getRemoteIPAddress() 
200                                + " is restricted.  Contact the postmaster for details.");
201                    } else {
202                        return new HookResult(HookReturnCode.DENY,DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH) + " " + blocklistedDetail);
203                    }
204                   
205                }
206            }
207            return new HookResult(HookReturnCode.DECLINED);
208        }
209    }