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 }