Index: java/org/apache/catalina/filters/RequestFilter.java =================================================================== --- java/org/apache/catalina/filters/RequestFilter.java (revision 1178969) +++ java/org/apache/catalina/filters/RequestFilter.java (working copy) @@ -218,7 +218,7 @@ * @return true if this request should be allowed, * false otherwise */ - private boolean isAllowed(String property) { + protected boolean isAllowed(String property) { if (deny != null && deny.matcher(property).matches()) { return false; } Index: java/org/apache/catalina/filters/RemoteAddrNetmaskFilter.java =================================================================== --- java/org/apache/catalina/filters/RemoteAddrNetmaskFilter.java (revision 0) +++ java/org/apache/catalina/filters/RemoteAddrNetmaskFilter.java (revision 0) @@ -0,0 +1,284 @@ +package org.apache.catalina.filters; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; + + +import org.apache.catalina.comet.CometEvent; +import org.apache.catalina.comet.CometFilter; +import org.apache.catalina.comet.CometFilterChain; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + +/** + * Implementation of a Filter that performs filtering based on comparing the + * appropriate request property (selected based on which subclass you choose + * to configure into your Container's pipeline) against the regular expressions + * configured for this Filter. + *

+ * This filter is configured by setting the allow and/or + * deny properties to a regular expressions (in the syntax + * supported by {@link java.util.regex.Pattern}) to which the appropriate request property will + * be compared. Evaluation proceeds as follows: + *

+ */ + +public class RemoteAddrNetmaskFilter + extends RequestFilter +{ + // ----------------------------------------------------- Instance Variables + private static final Log log = LogFactory.getLog(RemoteAddrFilter.class); + + /** + * The matchers used to test for allowed requests. + */ + protected final List allow = new ArrayList(); + + /** + * The matchers used to test for denied requests. + */ + protected final List deny = new ArrayList(); + + // ------------------------------------------------------------- Properties + + // --------------------------------------------------------- Public Methods + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + + final String denies = super.getDeny(); + for(final String s: denies.split("\\s*,\\s*")) + { + try { + if(s.contains("/")) + deny.add(new NetMaskIPMatcher(s)); + else + deny.add(new StringIPMatcher(s)); + } catch (IllegalArgumentException e) { + // FIXME: log + } + } + + final String allows = super.getDeny(); + for(final String s: allows.split("\\s*,\\s*")) + { + try { + if(s.contains("/")) + allow.add(new NetMaskIPMatcher(s)); + else + allow.add(new StringIPMatcher(s)); + } catch (IllegalArgumentException e) { + // FIXME: log + } + } + + } + + public void doFilter(ServletRequest request, + ServletResponse response, + FilterChain chain) + throws ServletException, IOException + { + process(request.getRemoteAddr(), request, response, chain); + } + + /** + * Extract the desired request property, and pass it (along with the comet + * event and filter chain) to the protected process() method + * to perform the actual filtering. + * + * @param event The comet event to be processed + * @param chain The filter chain for this event + * + * @exception IOException if an input/output error occurs + * @exception ServletException if a servlet error occurs + */ + @Override + public void doFilterEvent(CometEvent event, CometFilterChain chain) + throws IOException, ServletException { + processCometEvent(event.getHttpServletRequest().getRemoteAddr(), + event, chain); + } + + // ------------------------------------------------------ Protected Methods + + @Override + protected boolean isAllowed(String property) { + final InetAddress addr; + + try { + addr = InetAddress.getByName(property); + } catch (UnknownHostException e) { + // FIXME: log + return false; + } + + for (final IPMatcher m: deny) + if (m.matches(addr)) + return false; + + for (final IPMatcher m: allow) + if (m.matches(addr)) + return true; + + // Allow if denies specified but not allows + if (!deny.isEmpty() && allow.isEmpty()) + return true; + + // Deny this request + return false; + } + + @Override + protected Log getLogger() { + return log; + } + + private interface IPMatcher + { + boolean matches(InetAddress address); + } + + static class StringIPMatcher + implements IPMatcher + { + private String address; + + public StringIPMatcher(final String address) + { + this.address = address; + } + + public boolean matches(final InetAddress address) + { + return address.getHostAddress().equals(this.address); + } + } + + + /** + * A class representing a netmask, which is at the core of this valve. + * + *

The constructor takes a {@link java.lang.String} representing a + * CIDR netmask as an argument and extracts two informations from it: the + * network address and the CIDR. It then turns the address into a + * {@link java.math.BigInteger}, calculates the right shift and shifts that + * BigInteger by it.

+ *

The process to verify whether an IP address falls within the mask + * is to also convert it to a BigInteger, shifting it right and comparing + * it to the stored BigInteger. + *

+ */ + static class NetMaskIPMatcher + implements IPMatcher + { + /** + * The argument to the constructor, used for .toString() + */ + private final String expression; + + /** + * The number of bits a matching candidate needs to be shifted right + * in order to see if it matches + */ + private final int shift; + + /** + * The network address, already shifted right + */ + private final BigInteger mask; + + /** + * Constructor. + * + * @param expression the CIDR netmask + * @throws IllegalArgumentException if the netmask is not correct + * (invalid address specification, malformed CIDR prefix, etc) + */ + public NetMaskIPMatcher(final String expression) { + final int idx = expression.indexOf("/"); + final int cidr, addrlen; + final String addressPart; + final InetAddress addr; + final byte[] bytes; + + if (idx == -1) { + cidr = -1; + addressPart = expression; + } else { + final String substring = expression.substring(idx + 1); + try { + cidr = Integer.parseInt(substring); + if (cidr < 0) + throw new NumberFormatException("CIDR is negative"); + } catch (NumberFormatException ignored) { + throw new IllegalArgumentException("provided CIDR mask (" + + substring + ") is invalid"); + } + addressPart = expression.substring(0, idx); + } + + try { + addr = InetAddress.getByName(addressPart); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("provided address (" + + addressPart + ") is invalid"); + } + + bytes = addr.getAddress(); + addrlen = bytes.length * 8; + shift = cidr == -1 ? 0 : addrlen - cidr; + + if (shift < 0) + throw new IllegalArgumentException("CIDR prefix (" + cidr + + ") is greater than address length (" + addrlen + ")"); + mask = new BigInteger(bytes).shiftRight(shift); + this.expression = expression; + } + + /** + * Test if a given address matches this netmask + * + * @param addr The {@link java.net.InetAddress} to test + * @return true on match, false otherwise + */ + public boolean matches (final InetAddress addr) { + final BigInteger provided = new BigInteger(addr.getAddress()) + .shiftRight(shift); + + return mask.equals(provided); + } + + @Override + public String toString() { + return expression; + } + } +}