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 }