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