View Javadoc

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