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         if (entries != null && entries.length > 0)
258         {
259             for (String addrPath:entries)
260             {
261                 add(addrPath, patternMap);
262             }
263         }
264     }
265     
266     /* ------------------------------------------------------------ */
267     /**
268      * Check if specified request is allowed by current IPAccess rules.
269      * 
270      * @param addr internet address
271      * @param path context path
272      * @return true if request is allowed
273      *
274      */
275     protected boolean isAddrUriAllowed(String addr, String path)
276     {
277         if (_white.size()>0)
278         {
279             boolean match = false;
280             
281             Object whiteObj = _white.getLazyMatches(addr);
282             if (whiteObj != null) 
283             {
284                 List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
285 
286                 for (Object entry: whiteList)
287                 {
288                     PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
289                     if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
290                         break;
291                 }
292             }
293             
294             if (!match)
295                 return false;
296         }
297 
298         if (_black.size() > 0)
299         {
300             Object blackObj = _black.getLazyMatches(addr);
301             if (blackObj != null) 
302             {
303                 List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
304     
305                 for (Object entry: blackList)
306                 {
307                     PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
308                     if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
309                         return false;
310                 }
311             }
312         }
313         
314         return true;
315     }
316 
317     /* ------------------------------------------------------------ */
318     /**
319      * Dump the white- and black-list configurations when started
320      * 
321      * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
322      */
323     @Override
324     protected void doStart()
325         throws Exception
326     {
327         super.doStart();
328         
329         if (Log.isDebugEnabled())
330         {
331             System.err.println(dump());
332         }
333     }
334     
335     /* ------------------------------------------------------------ */
336     /**
337      * Dump the handler configuration
338      */
339     public String dump()
340     {
341         StringBuilder buf = new StringBuilder();
342         
343         buf.append(toString());
344         buf.append(" WHITELIST:\n");
345         dump(buf, _white);
346         buf.append(toString());
347         buf.append(" BLACKLIST:\n");
348         dump(buf, _black);
349         
350         return buf.toString();
351     }    
352     
353     /* ------------------------------------------------------------ */
354     /**
355      * Dump a pattern map into a StringBuilder buffer
356      * 
357      * @param buf buffer
358      * @param patternMap pattern map to dump
359      */
360     protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
361     {
362         for (String addr: patternMap.keySet())
363         {
364             for (Object path: ((PathMap)patternMap.get(addr)).values())
365             {
366                 buf.append("# ");
367                 buf.append(addr);
368                 buf.append("|");
369                 buf.append(path);
370                 buf.append("\n");
371             }       
372         }
373     }
374  }