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