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