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