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 }