View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.servlet.ServletException;
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  
31  import org.eclipse.jetty.http.HttpStatus;
32  import org.eclipse.jetty.http.PathMap;
33  import org.eclipse.jetty.io.EndPoint;
34  import org.eclipse.jetty.server.HttpChannel;
35  import org.eclipse.jetty.server.Request;
36  import org.eclipse.jetty.util.IPAddressMap;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  
40  
41  /**
42   * IP Access Handler
43   * <p>
44   * Controls access to the wrapped handler by the real remote IP. Control is provided
45   * by white/black lists that include both internet addresses and URIs. This handler
46   * uses the real internet address of the connection, not one reported in the forwarded
47   * for headers, as this cannot be as easily forged.
48   * <p>
49   * Typically, the black/white lists will be used in one of three modes:
50   * <ul>
51   * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
52   * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
53   * <li>Allowing a general range of IPs/URLs by specifying several general white list
54   * entries, that are then further refined by several specific black list exceptions
55   * </ul>
56   * <p>
57   * An empty white list is treated as match all. If there is at least one entry in
58   * the white list, then a request must match a white list entry. Black list entries
59   * are always applied, so that even if an entry matches the white list, a black list
60   * entry will override it.
61   * <p>
62   * Internet addresses may be specified as absolute address or as a combination of
63   * four octet wildcard specifications (a.b.c.d) that are defined as follows.
64   * </p>
65   * <pre>
66   * nnn - an absolute value (0-255)
67   * mmm-nnn - an inclusive range of absolute values,
68   *           with following shorthand notations:
69   *           nnn- => nnn-255
70   *           -nnn => 0-nnn
71   *           -    => 0-255
72   * a,b,... - a list of wildcard specifications
73   * </pre>
74   * <p>
75   * Internet address specification is separated from the URI pattern using the "|" (pipe)
76   * character. URI patterns follow the servlet specification for simple * prefix and
77   * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
78   * <p>
79   * Earlier versions of the handler used internet address prefix wildcard specification
80   * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
81   * They also used the first "/" character of the URI pattern to separate it from the
82   * internet address. Both of these features have been deprecated in the current version.
83   * <p>
84   * Examples of the entry specifications are:
85   * <ul>
86   * <li>10.10.1.2 - all requests from IP 10.10.1.2
87   * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
88   * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
89   * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
90   * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
91   * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
92   * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
93   *                                  to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
94   * </ul>
95   * <p>
96   * Earlier versions of the handler used internet address prefix wildcard specification
97   * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
98   * They also used the first "/" character of the URI pattern to separate it from the
99   * internet address. Both of these features have been deprecated in the current version.
100  */
101 public class IPAccessHandler extends HandlerWrapper
102 {
103     private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
104 
105     IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>();
106     IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>();
107 
108     /* ------------------------------------------------------------ */
109     /**
110      * Creates new handler object
111      */
112     public IPAccessHandler()
113     {
114         super();
115     }
116 
117     /* ------------------------------------------------------------ */
118     /**
119      * Creates new handler object and initializes white- and black-list
120      *
121      * @param white array of whitelist entries
122      * @param black array of blacklist entries
123      */
124     public IPAccessHandler(String[] white, String []black)
125     {
126         super();
127 
128         if (white != null && white.length > 0)
129             setWhite(white);
130         if (black != null && black.length > 0)
131             setBlack(black);
132     }
133 
134     /* ------------------------------------------------------------ */
135     /**
136      * Add a whitelist entry to an existing handler configuration
137      *
138      * @param entry new whitelist entry
139      */
140     public void addWhite(String entry)
141     {
142         add(entry, _white);
143     }
144 
145     /* ------------------------------------------------------------ */
146     /**
147      * Add a blacklist entry to an existing handler configuration
148      *
149      * @param entry new blacklist entry
150      */
151     public void addBlack(String entry)
152     {
153         add(entry, _black);
154     }
155 
156     /* ------------------------------------------------------------ */
157     /**
158      * Re-initialize the whitelist of existing handler object
159      *
160      * @param entries array of whitelist entries
161      */
162     public void setWhite(String[] entries)
163     {
164         set(entries, _white);
165     }
166 
167     /* ------------------------------------------------------------ */
168     /**
169      * Re-initialize the blacklist of existing handler object
170      *
171      * @param entries array of blacklist entries
172      */
173     public void setBlack(String[] entries)
174     {
175         set(entries, _black);
176     }
177 
178     /* ------------------------------------------------------------ */
179     /**
180      * Checks the incoming request against the whitelist and blacklist
181      *
182      * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
183      */
184     @Override
185     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
186     {
187         // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
188         HttpChannel channel = baseRequest.getHttpChannel();
189         if (channel!=null)
190         {
191             EndPoint endp=channel.getEndPoint();
192             if (endp!=null)
193             {
194                 InetSocketAddress address = endp.getRemoteAddress();
195                 if (address!=null && !isAddrUriAllowed(address.getHostString(),baseRequest.getPathInfo()))
196                 {
197                     response.sendError(HttpStatus.FORBIDDEN_403);
198                     baseRequest.setHandled(true);
199                     return;
200                 }
201             }
202         }
203 
204         getHandler().handle(target,baseRequest, request, response);
205     }
206 
207 
208     /* ------------------------------------------------------------ */
209     /**
210      * Helper method to parse the new entry and add it to
211      * the specified address pattern map.
212      *
213      * @param entry new entry
214      * @param patternMap target address pattern map
215      */
216     protected void add(String entry, IPAddressMap<PathMap> patternMap)
217     {
218         if (entry != null && entry.length() > 0)
219         {
220             boolean deprecated = false;
221             int idx;
222             if (entry.indexOf('|') > 0 )
223             {
224                 idx = entry.indexOf('|');
225             }
226             else
227             {
228                 idx = entry.indexOf('/');
229                 deprecated = (idx >= 0);
230             }
231 
232             String addr = idx > 0 ? entry.substring(0,idx) : entry;
233             String path = idx > 0 ? entry.substring(idx) : "/*";
234 
235             if (addr.endsWith("."))
236                 deprecated = true;
237             if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
238                 path=path.substring(1);
239 
240             PathMap pathMap = patternMap.get(addr);
241             if (pathMap == null)
242             {
243                 pathMap = new PathMap(true);
244                 patternMap.put(addr,pathMap);
245             }
246             if (path != null && !"".equals(path))
247                 pathMap.put(path,path);
248 
249             if (deprecated)
250                 LOG.debug(toString() +" - deprecated specification syntax: "+entry);
251         }
252     }
253 
254     /* ------------------------------------------------------------ */
255     /**
256      * Helper method to process a list of new entries and replace
257      * the content of the specified address pattern map
258      *
259      * @param entries new entries
260      * @param patternMap target address pattern map
261      */
262     protected void set(String[] entries,  IPAddressMap<PathMap> patternMap)
263     {
264         patternMap.clear();
265 
266         if (entries != null && entries.length > 0)
267         {
268             for (String addrPath:entries)
269             {
270                 add(addrPath, patternMap);
271             }
272         }
273     }
274 
275     /* ------------------------------------------------------------ */
276     /**
277      * Check if specified request is allowed by current IPAccess rules.
278      *
279      * @param addr internet address
280      * @param path context path
281      * @return true if request is allowed
282      *
283      */
284     protected boolean isAddrUriAllowed(String addr, String path)
285     {
286         if (_white.size()>0)
287         {
288             boolean match = false;
289 
290             Object whiteObj = _white.getLazyMatches(addr);
291             if (whiteObj != null)
292             {
293                 List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
294 
295                 for (Object entry: whiteList)
296                 {
297                     PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
298                     if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
299                         break;
300                 }
301             }
302 
303             if (!match)
304                 return false;
305         }
306 
307         if (_black.size() > 0)
308         {
309             Object blackObj = _black.getLazyMatches(addr);
310             if (blackObj != null)
311             {
312                 List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
313 
314                 for (Object entry: blackList)
315                 {
316                     PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
317                     if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
318                         return false;
319                 }
320             }
321         }
322 
323         return true;
324     }
325 
326     /* ------------------------------------------------------------ */
327     /**
328      * Dump the handler configuration
329      */
330     public String dump()
331     {
332         StringBuilder buf = new StringBuilder();
333 
334         buf.append(toString());
335         buf.append(" WHITELIST:\n");
336         dump(buf, _white);
337         buf.append(toString());
338         buf.append(" BLACKLIST:\n");
339         dump(buf, _black);
340 
341         return buf.toString();
342     }
343 
344     /* ------------------------------------------------------------ */
345     /**
346      * Dump a pattern map into a StringBuilder buffer
347      *
348      * @param buf buffer
349      * @param patternMap pattern map to dump
350      */
351     protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
352     {
353         for (String addr: patternMap.keySet())
354         {
355             for (Object path: ((PathMap)patternMap.get(addr)).values())
356             {
357                 buf.append("# ");
358                 buf.append(addr);
359                 buf.append("|");
360                 buf.append(path);
361                 buf.append("\n");
362             }
363         }
364     }
365  }