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