001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /* $Id: AbstractIPRange.java 531479 2007-04-23 14:21:35Z andreas $ */
020:
021: package org.apache.lenya.ac.impl;
022:
023: import java.io.File;
024: import java.net.InetAddress;
025: import java.net.UnknownHostException;
026: import java.util.Arrays;
027:
028: import org.apache.avalon.framework.logger.Logger;
029: import org.apache.lenya.ac.AccessControlException;
030: import org.apache.lenya.ac.IPRange;
031: import org.apache.lenya.ac.ItemManager;
032: import org.apache.lenya.ac.Machine;
033: import org.apache.lenya.net.InetAddressUtil;
034:
035: /**
036: * <p>
037: * A range of IP addresses, expressed by a network address and a subnet mask.
038: * </p>
039: * <p>
040: * Note: this class does not enforce that the network address and the subnet mask have the same size
041: * (i.e. either both IPv4 or both IPv6 addresses). If the the network address and subnet mask have
042: * different sizes, the range does not contain any hosts, that is {@link #contains(Machine)} will
043: * always return <code>false</code>.
044: * </p>
045: */
046: public abstract class AbstractIPRange extends AbstractGroupable
047: implements IPRange {
048: /*
049: * FIXME by zisch@dals.ch: Fixed this class for IPv6. However there are still some general
050: * flaws, partly coming from the IPRange interface. A redesign of (Abstract/File)IPRange and
051: * it's helper class org.apache.lenya.net.InetAddressUtil would be a good idea. Some problems of
052: * this implementation are:
053: * - The whole initialization seems flawed. Objects can be in an unitialized state and the
054: * class seems not to be aware of this.
055: * - Network-address and -mask can be set independently. Therefore it cannot be enforced that
056: * these have the same size (i.e. that both are IPv4 or both are IPv6). This shows up in
057: * InetAddressUtil.contains(...), where in a case of mismatch there is no good way to inform the
058: * user about the problem. This should be done once when the AbstractIPRange object is
059: * initialized.
060: * - Unless this functionality would be needed by other parts of Lenya or external software
061: * (which seems not to be the case ;-), InetAddressUtil should be removed (resp. deprecated)
062: * altogether, because it's mostly an internal implementation detail of AbstractIPRange.
063: * AbstractIPRange should implement the contains(...)-method internally to make use of the fact
064: * that the network- addresses and -masks validity and compatibility has already been checked
065: * when setting these. (Once the above problems have been fixed. ;-)
066: * - Especially for IPv6 it would be nice to have the possibility to specify the netmask as the
067: * number of bits (as in "::1/128" or "127.0.0.1/24").
068: *
069: * FIXME II (from the previous version): why are we in the business of implementing IP ranges??
070: */
071:
072: /**
073: * Ctor.
074: * Initializes the the IP range with the local host (127.0.0.1/24 for IPv4, ::1/128 for IPv6).
075: * @param itemManager The item manager.
076: * @param logger The logger.
077: */
078: public AbstractIPRange(ItemManager itemManager, Logger logger) {
079: super (itemManager, logger);
080: try {
081: this .networkAddress = InetAddress.getLocalHost();
082: byte[] mask = null;
083: int masklen = this .networkAddress.getAddress().length;
084: if (masklen == 4) {
085: /* IPv4: */
086: /*
087: * FIXME? by zisch@dals.ch: Should this be { -1, 0, 0, 0 }??
088: */
089: mask = new byte[] { -1, -1, -1, 0 };
090: } else {
091: /* IPv6 (and others ;-): */
092: mask = new byte[masklen];
093: Arrays.fill(mask, (byte) -1);
094: }
095: this .subnetMask = InetAddress.getByAddress(mask);
096: } catch (UnknownHostException ignore) {
097: /*
098: * FIXME? by zisch@dals.ch: Is it safe to ignore the exception and just leave the
099: * IPRange uninitialized!?
100: */
101: }
102: }
103:
104: /**
105: * Ctor.
106: * @param itemManager The item manager.
107: * @param logger The logger.
108: * @param id The IP range ID.
109: */
110: public AbstractIPRange(ItemManager itemManager, Logger logger,
111: String id) {
112: super (itemManager, logger);
113: setId(id);
114: }
115:
116: private File configurationDirectory;
117:
118: /**
119: * Returns the configuration directory.
120: * @return A file object.
121: */
122: public File getConfigurationDirectory() {
123: return this .configurationDirectory;
124: }
125:
126: protected void setConfigurationDirectory(
127: File _configurationDirectory) {
128: this .configurationDirectory = _configurationDirectory;
129: }
130:
131: /**
132: * Save the IP range
133: * @throws AccessControlException if the save failed
134: */
135: public abstract void save() throws AccessControlException;
136:
137: /**
138: * Delete an IP range
139: * @throws AccessControlException if the delete failed
140: */
141: public void delete() throws AccessControlException {
142: removeFromAllGroups();
143: }
144:
145: private InetAddress networkAddress;
146:
147: /**
148: * Sets the network address. This method accepts numeric IPv4 addresses like
149: * <code>"129.168.0.32"</code>, numeric IPv6 addresses like
150: * <code>"1080::8:800:200C:417A"</code> as well as hostnames (if DNS resolution is available)
151: * like <code>"localhost"</code> or <code>"www.apache.com"</code>.
152: * @param address a <code>String</code> like <code>"192.168.0.32"</code>,
153: * <code>"::1"</code>, ...
154: * @throws AccessControlException when the conversion of the <code>String</code> to an
155: * <code>InetAddress</code> failed
156: * @see #setNetworkAddress(byte[])
157: */
158: public void setNetworkAddress(String address)
159: throws AccessControlException {
160: try {
161: this .networkAddress = InetAddress.getByName(address);
162: } catch (UnknownHostException e) {
163: throw new AccessControlException(
164: "Failed to convert address [" + address + "]: ", e);
165: }
166: }
167:
168: /**
169: * Sets the network address. The method accepts numeric IPv4 addresses (specified by byte arrays
170: * of length 4) or IPv6 addresses (specified by byte arrays of length 16).
171: * @param address a byte array of the length 4 or 16
172: * @throws AccessControlException when the conversion of the byte array to an InetAddress
173: * failed.
174: * @see #setNetworkAddress(String)
175: */
176: public void setNetworkAddress(byte[] address)
177: throws AccessControlException {
178: try {
179: this .networkAddress = InetAddress.getByAddress(address);
180: } catch (UnknownHostException e) {
181: throw new AccessControlException(
182: "Failed to convert address ["
183: + addr2string(address) + "]: ", e);
184: }
185: }
186:
187: /**
188: * Returns the network address.
189: * @return an <code>InetAddress</code> representing the network address
190: */
191: public InetAddress getNetworkAddress() {
192: return this .networkAddress;
193: }
194:
195: private InetAddress subnetMask;
196:
197: /**
198: * Sets the subnet mask. See {@link #setNetworkAddress(String)} for the allowed formats of the
199: * <code>mask</code> string. (However, the hostname format will usually not be of much use for
200: * setting the mask.)
201: * <p>
202: * Only valid subnet masks are accepted, for which the binary representation is a sequence of
203: * 1-bits followed by a sequence of 0-bits. For example <code>"255.128.0.0"</code> is valid
204: * while <code>"255.128.0.1"</code> is not.
205: * @param mask a <code>String</code> like <code>"255.255.255.0"</code>
206: * @throws AccessControlException when the conversion of the String to an
207: * <code>InetAddress</code> failed.
208: * @see #setSubnetMask(byte[])
209: */
210: public void setSubnetMask(String mask)
211: throws AccessControlException {
212: try {
213: /* use setSubnetMask(...) to check the mask-format: */
214: setSubnetMask(InetAddress.getByName(mask).getAddress());
215: } catch (final UnknownHostException e) {
216: throw new AccessControlException("Failed to convert mask ["
217: + mask + "]: ", e);
218: }
219:
220: }
221:
222: /**
223: * Sets the subnet mask.
224: * <p>
225: * Only valid subnet masks are accepted, for which the binary representation is a sequence of
226: * 1-bits followed by a sequence of 0-bits. For example <code>{ 255, 128, 0, 0 }</code> is
227: * valid while <code>{ 255, 128, 0, 1 }</code> is not.
228: * @param mask A byte array of the length 4.
229: * @throws AccessControlException when the conversion of the byte array to an InetAddress
230: * failed.
231: * @see #setSubnetMask(String)
232: */
233: public void setSubnetMask(byte[] mask)
234: throws AccessControlException {
235: /*
236: * check for correct netmask (i.e. any number of 1-bits followed by 0-bits filling the right
237: * part of the mask) ...
238: *
239: * FIXME: This "algorithm" is rather unelegant. There should be a better way to do it! ;-)
240: */
241: if (getLogger().isDebugEnabled()) {
242: getLogger().debug(
243: "CHECK_NETMASK: check " + addr2string(mask));
244: }
245: int i = 0;
246: CHECK_NETMASK: while (i < mask.length) {
247: int b = mask[i++] & 0xff;
248: /* the initial byte(s) must be 255: */
249: if (b != 0xff) {
250: /* first byte != 255, test all possibilities: */
251: if (getLogger().isDebugEnabled()) {
252: getLogger().debug(
253: "CHECK_NETMASK: first byte != 255: idx: "
254: + (i - 1) + ", mask[idx]: 0x" + b);
255: }
256: /* check if 0: */
257: if (b == 0) {
258: break CHECK_NETMASK;
259: }
260: for (int tst = 0xfe; tst != 0; tst = (tst << 1) & 0xff) {
261: getLogger().debug(
262: "CHECK_NETMASK: tst == 0x"
263: + Integer.toHexString(tst));
264: if (b == tst) {
265: break CHECK_NETMASK;
266: }
267: }
268: /*
269: * Invalid byte found, i.e. one which is not element of { 11111111, 11111110,
270: * 11111100, 11111000, ..., 00000000 }
271: */
272: throw new AccessControlException(
273: "Invalid byte in mask [" + addr2string(mask)
274: + "]");
275: }
276: }
277: /* the remaining byte(s) (if any) must be 0: */
278: while (++i < mask.length) {
279: if (mask[i] != 0) {
280: /*
281: * Invalid byte found, i.e. some non-zero byte right of the first non-zero byte.
282: */
283: throw new AccessControlException(
284: "Invalid non-zero byte in mask ["
285: + addr2string(mask) + "]");
286: }
287: }
288:
289: /* convert the checked mask to InetAddress: */
290: try {
291: this .subnetMask = InetAddress.getByAddress(mask);
292: } catch (final UnknownHostException e) {
293: throw new AccessControlException("Failed to convert mask ["
294: + addr2string(mask) + "]: ", e);
295: }
296: }
297:
298: /**
299: * Returns the subnet mask.
300: * @return An InetAddress value.
301: */
302: public InetAddress getSubnetMask() {
303: return this .subnetMask;
304: }
305:
306: /**
307: * Checks if a network address / subnet mask combination describes a valid subnet.
308: * @param networkAddress The network address.
309: * @param subnetMask The subnet mask.
310: * @return A boolean value.
311: * @deprecated This method is currently not implemented, probably not necessary.and could be
312: * removed in the future. Therefore it should not be used.
313: */
314: public static boolean isValidSubnet(InetAddress networkAddress,
315: InetAddress subnetMask) {
316: /*
317: * FIXME? by zisch@dals.ch: Is this method really necessary (what for?) and (if so)
318: * shouldn't it be an internal (private) utility-method??
319: */
320: // TODO implement class
321: return false;
322: }
323:
324: /**
325: * Checks if this IP range contains a certain machine.
326: * <p>
327: * Note: if the network address and the subnet mask of this IP range have different sizes (i.e.
328: * one is IPv4 and one is IPv6), this method will always return <code>false</code>, no matter
329: * what machine has been specified!
330: * <p>
331: * Further, if the machine address and the IP range (i.e. network address and subnet mask) have
332: * different sizes, the method will return <code>false</code>. (In other words: an IPv4 range
333: * never contains an IPv6 address and the other way round.)
334: * <p>
335: * Note that the above can lead to confusion. For example the local subnet in IPv4 (
336: * <code>127.0.0.0/8</code>) will <b>not </b> contain the localhost in IPv6 (
337: * <code>::1</code>), and the localhost in IPv4 (<code>127.0.0.1</code>) will <b>not </b>
338: * be contained in the local subnet in IPv6 (<code>::1/128</code>).
339: * @param machine the machine to check for
340: * @return a boolean value
341: * @see InetAddressUtil#contains
342: */
343: public boolean contains(Machine machine) {
344: /*
345: * FIXME? by zisch@dals.ch: Maybe some mapping between IPv4/v6 should be done here, p.e. for
346: * the localhost (see the javdoc comment above)? (I'm not a TCP/IP-guru, so I'm not sure
347: * about this. ;-)
348: */
349: getLogger().debug("Checking IP range: [" + getId() + "]");
350: InetAddressUtil util = new InetAddressUtil(getLogger());
351: return util.contains(this .networkAddress, this .subnetMask,
352: machine.getAddress());
353: }
354:
355: /**
356: * Format the specified numeric IP address.
357: * @param addr the raw numeric IP address
358: * @return the formatted address
359: */
360: private static String addr2string(byte[] addr) {
361: StringBuffer buf = new StringBuffer();
362: if (addr.length > 4) {
363: /* IPv6-format if more than 4 bytes: */
364: for (int i = 0; i < addr.length; i++) {
365: if (i > 0 && (i & 1) == 0) {
366: buf.append(':');
367: }
368: String hex = Integer.toHexString(addr[i] & 0xff);
369: if (hex.length() == 1) {
370: buf.append('0');
371: }
372: buf.append(hex);
373: }
374: } else {
375: /* IPv4-format: */
376: for (int i = 0; i < addr.length; i++) {
377: if (i > 0) {
378: buf.append('.');
379: }
380: buf.append(addr[i] & 0xff);
381: }
382: }
383: return buf.toString();
384: }
385: }
|