001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.server.security;
030:
031: import com.caucho.config.ConfigException;
032: import com.caucho.log.Log;
033: import com.caucho.util.InetNetwork;
034: import com.caucho.util.L10N;
035: import com.caucho.util.LruCache;
036:
037: import javax.annotation.PostConstruct;
038: import javax.servlet.ServletContext;
039: import javax.servlet.ServletException;
040: import javax.servlet.http.HttpServletRequest;
041: import javax.servlet.http.HttpServletResponse;
042: import java.io.IOException;
043: import java.util.ArrayList;
044: import java.util.logging.Logger;
045:
046: /**
047: * Allow or deny requests based on the ip address of the client.
048: *
049: * <pre>
050: * <security-constraint>
051: * <ip-constraint>
052: * <allow>192.168.17.0/24</allow>
053: * </ip-constraint>
054: *
055: * <web-resource-collection>
056: * <url-pattern>/admin/*</url-pattern>
057: * </web-resource-collection>
058: * </security-constraint>
059: * </pre>
060: *
061: * <pre>
062: * <security-constraint>
063: * <ip-constraint>
064: * <deny>205.11.12.3</deny>
065: * <deny>213.43.62.45</deny>
066: * <deny>123.4.45.6</deny>
067: * <deny>233.15.25.35</deny>
068: * <deny>233.14.87.12</deny>
069: * </ip-constraint>
070: *
071: * <web-resource-collection>
072: * <url-pattern>/*</url-pattern>
073: * </web-resource-collection>
074: * </security-constraint>
075: * </pre>
076: */
077: public class IPConstraint extends AbstractConstraint {
078: static final Logger log = Log.open(IPConstraint.class);
079: static L10N L = new L10N(IPConstraint.class);
080:
081: private ArrayList<InetNetwork> _allowNetworkList;
082: private ArrayList<InetNetwork> _denyNetworkList;
083:
084: private int _cacheSize = 256;
085: private int _errorCode = HttpServletResponse.SC_FORBIDDEN;
086: private String _errorMessage = L.l("Forbidden IP Address");
087:
088: private LruCache<String, Boolean> _cache;
089:
090: /** see method SecurityConstraint.addIPConstraint() for explanation */
091: private boolean _oldStyle = false;
092:
093: public IPConstraint() {
094: }
095:
096: /**
097: * The error code to send with response.sendError, default is 403.
098: */
099: public void setErrorCode(int errorCode) {
100: _errorCode = errorCode;
101: }
102:
103: /**
104: * The error code to send with response.sendError, default is 403.
105: */
106: public int getErrorCode() {
107: return _errorCode;
108: }
109:
110: /**
111: * The error message to send with response.sendError, default is
112: * "Forbidden IP Address"
113: */
114: public void setErrorMessage(String errorMessage) {
115: _errorMessage = errorMessage;
116: }
117:
118: /**
119: * The error message to send with response.sendError, default is
120: * "Forbidden IP Address"
121: */
122: public String getErrorMessage() {
123: return _errorMessage;
124: }
125:
126: /**
127: * Size of the cache used to hold whether or not to allow a certain IP
128: * address, default is 256. The first time a request is received from an ip,
129: * the allow and deny rules are checked to determine if the ip is allowed.
130: * The result of this check is cached in a an LRU cache. Subsequent requests
131: * can do a cache lookup based on the ip instead of checking the rules. This
132: * is especially important if there are a large number of allow and/or deny
133: * rules, and to protect against denial of service attacks.
134: */
135: public void setCacheSize(int cacheSize) {
136: _cacheSize = cacheSize;
137: }
138:
139: /**
140: * Size of the cache used to hold whether or not to allow a certain IP
141: * address.
142: */
143: public int getCacheSize() {
144: return _cacheSize;
145: }
146:
147: /**
148: * Add an ip network to allow. If allow is never used, (only deny is used),
149: * then all are allowed except those in deny.
150: */
151: public void addAllow(String network) {
152: if (_allowNetworkList == null)
153: _allowNetworkList = new ArrayList<InetNetwork>();
154:
155: _allowNetworkList.add(InetNetwork.create(network));
156: }
157:
158: /**
159: * Add an ip network to deny.
160: */
161: public void addDeny(String network) {
162: if (_denyNetworkList == null)
163: _denyNetworkList = new ArrayList<InetNetwork>();
164:
165: _denyNetworkList.add(InetNetwork.create(network));
166: }
167:
168: /** backwards compatibility, same as addAllow() */
169: public void addText(String network) {
170: _oldStyle = true;
171: addAllow(network);
172: }
173:
174: /** backwards compatibility, used by SecurityConstraint.addIPConstraint() */
175: boolean isOldStyle() {
176: return _oldStyle;
177: }
178:
179: /** backwards compatibility, used by SecurityConstraint.addIPConstraint() */
180: void copyInto(IPConstraint target) {
181: if (_allowNetworkList != null) {
182: for (int i = 0; i < _allowNetworkList.size(); i++) {
183: target.addAllowInetNetwork(_allowNetworkList.get(i));
184: }
185: }
186: if (_denyNetworkList != null) {
187: for (int i = 0; i < _denyNetworkList.size(); i++) {
188: target.addDenyInetNetwork(_denyNetworkList.get(i));
189: }
190: }
191: }
192:
193: private void addAllowInetNetwork(InetNetwork a) {
194: if (_allowNetworkList == null)
195: _allowNetworkList = new ArrayList<InetNetwork>();
196: _allowNetworkList.add(a);
197: }
198:
199: private void addDenyInetNetwork(InetNetwork d) {
200: if (_denyNetworkList == null)
201: _denyNetworkList = new ArrayList<InetNetwork>();
202: _denyNetworkList.add(d);
203: }
204:
205: @PostConstruct
206: public void init() throws ConfigException {
207: if (_allowNetworkList == null && _denyNetworkList == null)
208: throw new ConfigException(L.l(
209: "either `{0}' or `{1}' or both are expected",
210: "<allow>", "<deny>"));
211:
212: if (_allowNetworkList != null)
213: _allowNetworkList.trimToSize();
214:
215: if (_denyNetworkList != null)
216: _denyNetworkList.trimToSize();
217:
218: int rules = _allowNetworkList == null ? 0 : _allowNetworkList
219: .size();
220: rules += _denyNetworkList == null ? 0 : _denyNetworkList.size();
221:
222: _cache = new LruCache<String, Boolean>(_cacheSize);
223: }
224:
225: /**
226: * Returns true if the user is authorized for the resource.
227: */
228: public boolean isAuthorized(HttpServletRequest request,
229: HttpServletResponse response, ServletContext application)
230: throws ServletException, IOException {
231: String remoteAddr = request.getRemoteAddr();
232: long addr = 0;
233: boolean allow = false;
234:
235: if (remoteAddr != null) {
236: if (_cache != null) {
237: Boolean cacheValue = _cache.get(remoteAddr);
238: if (cacheValue != null) {
239: allow = cacheValue.booleanValue();
240:
241: if (!allow)
242: response.sendError(_errorCode, _errorMessage);
243:
244: return allow;
245: }
246: }
247:
248: int len = remoteAddr.length();
249: int ch;
250: int i = 0;
251:
252: while (i < len && (ch = remoteAddr.charAt(i)) >= '0'
253: && ch <= '9') {
254: int digit = 0;
255:
256: for (; i < len && (ch = remoteAddr.charAt(i)) >= '0'
257: && ch <= '9'; i++)
258: digit = 10 * digit + ch - '0';
259:
260: addr = 256 * addr + digit;
261:
262: if (ch == '.')
263: i++;
264: }
265: }
266:
267: // check allow
268:
269: if (_allowNetworkList == null) {
270: // if no allow specified, then allow all
271: allow = true;
272: } else {
273: for (int i = 0; i < _allowNetworkList.size(); i++) {
274: InetNetwork net = _allowNetworkList.get(i);
275:
276: if (net.isMatch(addr)) {
277: allow = true;
278: break;
279: }
280: }
281: }
282:
283: // check deny
284:
285: if (allow && _denyNetworkList != null) {
286: for (int i = 0; i < _denyNetworkList.size(); i++) {
287: InetNetwork net = _denyNetworkList.get(i);
288:
289: if (net.isMatch(addr)) {
290: allow = false;
291: break;
292: }
293: }
294: }
295:
296: // update cache
297:
298: if (_cache != null)
299: _cache
300: .put(remoteAddr, allow ? Boolean.TRUE
301: : Boolean.FALSE);
302:
303: // respond accordingly
304:
305: if (!allow)
306: response.sendError(_errorCode, _errorMessage);
307:
308: return allow;
309:
310: }
311:
312: }
|