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