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 }