001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.authorization.dseecompat;
018
019import static org.opends.messages.AccessControlMessages.*;
020
021import java.net.Inet6Address;
022import java.net.InetAddress;
023import java.net.UnknownHostException;
024import java.util.BitSet;
025import java.util.HashMap;
026import java.util.Map;
027
028import org.forgerock.i18n.LocalizableMessage;
029
030/**
031 * A class representing a single IP address parsed from a IP bind rule
032 * expression. The class can be used to evaluate a remote clients IP address
033 * using the information parsed from the IP bind rule expression.
034 */
035public class PatternIP {
036    /** Enumeration that represents if the pattern is IPv5 or IPv4. */
037    private enum IPType {
038        IPv4, IPv6
039    }
040
041    /** The IP address type (v6 or v4). */
042    private final IPType ipType;
043
044    /** IPv4 sizes of addresses and prefixes. */
045    private static final int IN4ADDRSZ = 4;
046    private static final int IPV4MAXPREFIX = 32;
047
048    /** IPv6 sizes of addresses and prefixes. */
049    private static final int IN6ADDRSZ = 16;
050    private static final int IPV6MAXPREFIX = 128;
051
052    /**
053      Byte arrays used to match the remote IP address. The ruleAddrByte array
054      contains the bytes of the address from the ACI IP bind rule. The
055      rulePrefixBytes array contains the bytes of the cidr prefix or netmask
056      representation.
057     */
058    private final byte[] ruleAddrBytes, rulePrefixBytes;
059
060    /** Bit set that holds the wild-card information of processed IPv4 addresses. */
061    private final BitSet wildCardBitSet;
062
063    /** Map of valid netmask strings. Used in parsing netmask values. */
064    private static final Map<String, String> validNetMasks = new HashMap<>();
065    /** Initialize valid netmask hash map. */
066    static {
067        initNetMask(
068                "255.255.255.255",
069                "255.255.255.254",
070                "255.255.255.252",
071                "255.255.255.248",
072                "255.255.255.240",
073                "255.255.255.224",
074                "255.255.255.192",
075                "255.255.255.128",
076                "255.255.255.0",
077                "255.255.254.0",
078                "255.255.252.0",
079                "255.255.248.0",
080                "255.255.240.0",
081                "255.255.224.0",
082                "255.255.192.0",
083                "255.255.128.0",
084                "255.255.0.0",
085                "255.254.0.0",
086                "255.252.0.0",
087                "255.248.0.0",
088                "255.240.0.0",
089                "255.224.0.0",
090                "255.192.0.0",
091                "255.128.0.0",
092                "255.0.0.0",
093                "254.0.0.0",
094                "252.0.0.0",
095                "248.0.0.0",
096                "240.0.0.0",
097                "224.0.0.0",
098                "192.0.0.0",
099                "128.0.0.0",
100                "0.0.0.0"
101        );
102    }
103
104    /**
105     * Load the valid netmask hash map with the 33 possible valid netmask
106     * strings.
107     *
108      * @param lines The strings representing the valid netmasks.
109     */
110    private static void initNetMask(String... lines) {
111        for(String line : lines) {
112            validNetMasks.put(line, line);
113        }
114    }
115
116    /**
117     * Create a class that can be used to evaluate an IP address using the
118     * information decoded from the ACI IP bind rule expression.
119     *
120     * @param ipType The type of the ACI IP address (IPv4 or 6).
121     * @param ruleAddrBytes Byte array representing the ACI IP address.
122     * @param rulePrefixBytes Prefix byte array corresponding to the bits set
123     *                        by the cidr prefix or netmask.
124     * @param wildCardBitSet Bit set holding IPv4 wild-card information.
125     */
126    private PatternIP(IPType ipType, byte[] ruleAddrBytes,
127                      byte[] rulePrefixBytes, BitSet wildCardBitSet) {
128       this.ipType=ipType;
129       this.ruleAddrBytes=ruleAddrBytes;
130       this.rulePrefixBytes=rulePrefixBytes;
131       this.wildCardBitSet=wildCardBitSet;
132    }
133
134    /**
135     * Decode the provided address expression string and create a class that
136     * can be used to perform an evaluation of an IP address based on the
137     * decoded expression string information.
138     *
139     * @param expr The address expression string from the ACI IP bind rule.
140     * @return A class that can evaluate a remote clients IP address using the
141     *         expression's information.
142     * @throws AciException If the address expression is invalid.
143     */
144    public static
145    PatternIP decode(String expr)  throws AciException {
146        IPType ipType=IPType.IPv4;
147        byte[] prefixBytes;
148        String addrStr;
149        if(expr.indexOf(':') != -1) {
150            ipType = IPType.IPv6;
151        }
152        if(expr.indexOf('/') != -1) {
153            String prefixStr=null;
154            String[] s = expr.split("[/]", -1);
155            if(s.length == 2) {
156                prefixStr=s[1];
157            }
158            int prefix = getPrefixValue(ipType, s.length, expr, prefixStr);
159            prefixBytes=getPrefixBytes(prefix, ipType);
160            addrStr=s[0];
161        } else if(expr.indexOf('+') != -1) {
162            String netMaskStr=null;
163            String[] s = expr.split("[+]", -1);
164            if(s.length == 2) {
165                netMaskStr=s[1];
166            }
167            prefixBytes=getNetmaskBytes(netMaskStr, s.length, expr);
168            addrStr=s[0];
169        } else {
170            int prefix = getPrefixValue(ipType, 1, expr, null);
171            prefixBytes=getPrefixBytes(prefix, ipType);
172            addrStr=expr;
173        }
174        // Set the bit set size fo IN6ADDRSZ even though only 4 positions are used.
175        BitSet wildCardBitSet = new BitSet(IN6ADDRSZ);
176        byte[] addrBytes;
177        if(ipType == IPType.IPv4) {
178          addrBytes = procIPv4Addr(addrStr, wildCardBitSet, expr);
179        } else {
180            addrBytes=procIPv6Addr(addrStr, expr);
181            //The IPv6 address processed above might be a IPv4-compatible
182            //address, in which case only 4 bytes will be returned in the
183            //address byte  array. Ignore any IPv6 prefix.
184            if(addrBytes.length == IN4ADDRSZ) {
185                ipType=IPType.IPv4;
186                prefixBytes=getPrefixBytes(IPV4MAXPREFIX, ipType);
187            }
188        }
189        return new PatternIP(ipType, addrBytes, prefixBytes, wildCardBitSet);
190    }
191
192    /**
193     * Process the IP address prefix part of the expression. Handles if there is
194     * no prefix in the expression.
195     *
196     * @param ipType The type of the expression, either IPv6 or IPv4.
197     * @param numParts The number of parts in the IP address expression.
198     *                 1 if there isn't a prefix, and 2 if there is. Anything
199     *                 else is an error (i.e., 254.244.123.234/7/6).
200     * @param expr The original expression from the bind rule.
201     * @param prefixStr The string representation of the prefix part of the
202     *                  IP address.
203     * @return  An integer value determined from the prefix string.
204     * @throws AciException If the prefix string is invalid.
205     */
206    private static int
207    getPrefixValue(IPType ipType, int numParts, String expr, String prefixStr)
208    throws AciException {
209        int prefix = IPV4MAXPREFIX;
210        int maxPrefix= IPV4MAXPREFIX;
211        if(ipType == IPType.IPv6) {
212            prefix= IPV6MAXPREFIX;
213            maxPrefix=IPV6MAXPREFIX;
214        }
215        try {
216            //Can only have one prefix value and one address string.
217            if(numParts  < 1 || numParts > 2 ) {
218                LocalizableMessage message =
219                    WARN_ACI_SYNTAX_INVALID_PREFIX_FORMAT.get(expr);
220                throw new AciException(message);
221            }
222            if(prefixStr != null) {
223                prefix = Integer.parseInt(prefixStr);
224            }
225            //Must be between 0 to maxprefix.
226            if(prefix < 0 || prefix > maxPrefix) {
227                throw new AciException(WARN_ACI_SYNTAX_INVALID_PREFIX_VALUE.get(expr));
228            }
229            return prefix;
230        } catch(NumberFormatException nfex) {
231            throw new AciException(WARN_ACI_SYNTAX_PREFIX_NOT_NUMERIC.get(expr));
232        }
233    }
234
235    /**
236     * Determine the prefix bit mask based on the provided prefix value. Handles
237     * both IPv4 and IPv6 prefix values.
238     *
239     * @param prefix  The value of the prefix parsed from the address
240     *                expression.
241     * @param ipType  The type of the prefix, either IPv6 or IPv4.
242     * @return A byte array representing the prefix bit mask used to match
243     *         IP addresses.
244     */
245    private static byte[] getPrefixBytes(int prefix, IPType ipType) {
246        int i;
247        int maxSize=IN4ADDRSZ;
248        if(ipType==IPType.IPv6) {
249            maxSize= IN6ADDRSZ;
250        }
251        byte[] prefixBytes=new byte[maxSize];
252        for(i=0;prefix > 8 ; i++) {
253            prefixBytes[i] = (byte) 0xff;
254            prefix -= 8;
255        }
256        prefixBytes[i] = (byte) (0xff << 8 - prefix);
257        return prefixBytes;
258    }
259
260    /**
261     * Process the specified netmask string. Only pertains to IPv4 address
262     * expressions.
263     *
264     * @param netmaskStr String representation of the netmask parsed from the
265     *                   address expression.
266     * @param numParts The number of parts in the IP address expression.
267     *                 1 if there isn't a netmask, and 2 if there is. Anything
268     *                 else is an error (i.e., 254.244.123.234++255.255.255.0).
269     * @param expr The original expression from the bind rule.
270     * @return A byte array representing the netmask bit mask used to match
271     *         IP addresses.
272     * @throws AciException If the netmask string is invalid.
273     */
274    private static
275    byte[] getNetmaskBytes(String netmaskStr, int numParts, String expr)
276    throws AciException {
277        byte[] netmaskBytes=new byte[IN4ADDRSZ];
278        //Look up the string in the valid netmask hash table. If it isn't
279        //there it is an error.
280        if(!validNetMasks.containsKey(netmaskStr)) {
281            LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_NETMASK.get(expr);
282            throw new AciException(message);
283        }
284        //Can only have one netmask value and one address string.
285        if(numParts  < 1 || numParts > 2 ) {
286            LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_NETMASK_FORMAT.get(expr);
287            throw new AciException(message);
288        }
289        String[] s = netmaskStr.split("\\.", -1);
290        try {
291            for(int i=0; i < IN4ADDRSZ; i++) {
292                String quad=s[i].trim();
293                long val=Integer.parseInt(quad);
294                netmaskBytes[i] = (byte) (val & 0xff);
295            }
296        } catch (NumberFormatException nfex) {
297            LocalizableMessage message = WARN_ACI_SYNTAX_IPV4_NOT_NUMERIC.get(expr);
298            throw new AciException(message);
299        }
300        return netmaskBytes;
301    }
302
303    /**
304     * Process the provided IPv4 address string parsed from the IP bind rule
305     * address expression. It returns a byte array corresponding to the
306     * address string.  The specified bit set represents wild-card characters
307     * '*' found in the string.
308     *
309     * @param addrStr  A string representing an IPv4 address.
310     * @param wildCardBitSet A bit set used to save wild-card information.
311     * @param expr The original expression from the IP bind rule.
312     * @return A address byte array that can be used along with the prefix bit
313     *         mask to evaluate an IPv4 address.
314     *
315     * @throws AciException If the address string is not a valid IPv4 address
316     *                      string.
317     */
318    private static byte[]
319    procIPv4Addr(String addrStr, BitSet wildCardBitSet, String expr)
320    throws AciException {
321        byte[] addrBytes=new byte[IN4ADDRSZ];
322        String[] s = addrStr.split("\\.", -1);
323        try {
324            if(s.length != IN4ADDRSZ) {
325                LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_IPV4_FORMAT.get(expr);
326                throw new AciException(message);
327            }
328            for(int i=0; i < IN4ADDRSZ; i++) {
329                String quad=s[i].trim();
330                if(quad.equals("*")) {
331                    wildCardBitSet.set(i) ;
332                }
333                else {
334                    long val=Integer.parseInt(quad);
335                    //must be between 0-255
336                    if(val < 0 ||  val > 0xff) {
337                        LocalizableMessage message =
338                            WARN_ACI_SYNTAX_INVALID_IPV4_VALUE.get(expr);
339                        throw new AciException(message);
340                    }
341                    addrBytes[i] = (byte) (val & 0xff);
342                }
343            }
344        } catch (NumberFormatException nfex) {
345            LocalizableMessage message = WARN_ACI_SYNTAX_IPV4_NOT_NUMERIC.get(expr);
346            throw new AciException(message);
347        }
348        return addrBytes;
349    }
350
351    /**
352     * Process the provided IPv6  address string parsed from the IP bind rule
353     * IP expression. It returns a byte array corresponding to the
354     * address string. Wild-cards are not allowed in IPv6 addresses.
355     *
356     * @param addrStr A string representing an IPv6 address.
357     * @param expr The original expression from the IP bind rule.
358     * @return A address byte array that can be used along with the prefix bit
359     *         mask to evaluate an IPv6 address.
360     * @throws AciException If the address string is not a valid IPv6 address
361     *                      string.
362     */
363    private static byte[]
364    procIPv6Addr(String addrStr, String expr) throws AciException {
365        if(addrStr.indexOf('*') > -1) {
366            LocalizableMessage message = WARN_ACI_SYNTAX_IPV6_WILDCARD_INVALID.get(expr);
367            throw new AciException(message);
368        }
369        byte[] addrBytes;
370        try {
371            addrBytes=InetAddress.getByName(addrStr).getAddress();
372        } catch (UnknownHostException ex) {
373            LocalizableMessage message =
374                WARN_ACI_SYNTAX_INVALID_IPV6_FORMAT.get(expr, ex.getMessage());
375            throw new AciException(message);
376        }
377        return addrBytes;
378    }
379
380    /**
381     * Evaluate the provided IP address against the information processed during
382     * the IP bind rule expression decode.
383     *
384     * @param remoteAddr  A IP address to evaluate.
385     * @return An enumeration representing the result of the evaluation.
386     */
387    public EnumEvalResult evaluate(InetAddress remoteAddr) {
388        EnumEvalResult matched=EnumEvalResult.FALSE;
389        IPType ipType=IPType.IPv4;
390        byte[] addressBytes=remoteAddr.getAddress();
391        if(remoteAddr instanceof Inet6Address) {
392            ipType=IPType.IPv6;
393            Inet6Address addr6 = (Inet6Address) remoteAddr;
394            addressBytes= addr6.getAddress();
395            if(addr6.isIPv4CompatibleAddress()) {
396                ipType=IPType.IPv4;
397            }
398        }
399        if(ipType != this.ipType) {
400            return EnumEvalResult.FALSE;
401        }
402        if(matchAddress(addressBytes)) {
403            matched=EnumEvalResult.TRUE;
404        }
405        return matched;
406    }
407
408    /**
409     * Attempt to match the address byte array  using the  prefix bit mask array
410     * and the address byte array processed in the decode. Wild-cards take
411     * priority over the mask.
412     *
413     * @param addrBytes IP address byte array.
414     * @return True if the remote address matches based on the information
415     *         parsed from the IP bind rule expression.
416     */
417    private boolean matchAddress(byte[] addrBytes) {
418        if(wildCardBitSet.cardinality() == IN4ADDRSZ) {
419            return true;
420        }
421        for(int i=0;i <rulePrefixBytes.length; i++) {
422            if (!wildCardBitSet.get(i)
423                && (ruleAddrBytes[i] & rulePrefixBytes[i]) !=
424                    (addrBytes[i] & rulePrefixBytes[i]))
425            {
426              return false;
427            }
428        }
429        return true;
430    }
431}