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 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.types;
028    import org.opends.messages.Message;
029    
030    import org.opends.server.config.ConfigException;
031    import static org.opends.messages.ProtocolMessages.*;
032    import java.util.BitSet;
033    import java.net.Inet6Address;
034    import java.net.InetAddress;
035    import java.net.UnknownHostException;
036    
037    /**
038     * This class defines an address mask, which can be used to perform
039     * efficient comparisons against IP addresses to determine whether a
040     * particular IP address is in a given range.
041     */
042    @org.opends.server.types.PublicAPI(
043         stability=org.opends.server.types.StabilityLevel.VOLATILE,
044         mayInstantiate=true,
045         mayExtend=false,
046         mayInvoke=true)
047    public final class AddressMask
048    {
049      /**
050         * Types of rules we have.
051         *
052         * IPv4 - ipv4 rule
053         * IPv6 - ipv6 rule (begin with '[' or contains an ':').
054         * HOST - hostname match (foo.sun.com)
055         * HOSTPATTERN - host pattern match (begin with '.')
056         * ALLWILDCARD - *.*.*.* (first HOST is applied then ipv4)
057         *
058         */
059    
060         enum RuleType
061        {
062            IPv4, IPv6, HOSTPATTERN, ALLWILDCARD, HOST
063        }
064    
065        // Type of rule determined
066        private  RuleType ruleType;
067    
068        // IPv4 values for number of bytes and max CIDR prefix
069        /**
070         * IPv4 address size.
071         */
072        private static final int IN4ADDRSZ = 4;
073        private static final int IPV4MAXPREFIX = 32;
074    
075        // IPv6 values for number of bytes and max CIDR prefix
076        private static final int IN6ADDRSZ = 16;
077        private static final int IPV6MAXPREFIX = 128;
078    
079        //Holds binary representations of rule and mask respectively.
080        private  byte[] ruleMask, prefixMask;
081    
082        //Bit array that holds wildcard info for above binary arrays.
083        private final BitSet wildCard = new BitSet();
084    
085        //Array that holds each component of a hostname.
086        private  String[] hostName;
087    
088        //Holds a hostname pattern (ie, rule that begins with '.');'
089        private  String hostPattern;
090    
091        //Holds string passed into the constructor.
092        private  String ruleString;
093    
094        /**
095         * Address mask constructor.
096         * @param rule The rule string to process.
097         * @throws ConfigException If the rule string is not valid.
098         */
099        private AddressMask( String rule)
100        throws ConfigException
101        {
102            determineRuleType(rule);
103            switch (ruleType)
104            {
105            case IPv6:
106                processIPv6(rule);
107                 break;
108    
109            case IPv4:
110                processIpv4(rule);
111                break;
112    
113            case HOST:
114                processHost(rule);
115                break;
116    
117            case HOSTPATTERN:
118                processHostPattern(rule);
119                break;
120    
121            case ALLWILDCARD:
122                processAllWilds(rule);
123            }
124            ruleString=rule;
125        }
126    
127        /**
128         * Try to determine what type of rule string this is. See
129         * RuleType above for valid types.
130         * @param ruleString The rule string to be examined.
131         * @throws ConfigException If the rule type cannot be
132         *         determined from the rule string.
133         */
134        private void  determineRuleType(String ruleString)
135        throws ConfigException
136        {
137    
138            //Rule ending with '.' is invalid'
139            if(ruleString.endsWith("."))
140            {
141                Message message =
142                    ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
143                throw new ConfigException(message);
144            }
145            else if(ruleString.startsWith("."))
146            {
147                ruleType=RuleType.HOSTPATTERN;
148            }
149            else if(ruleString.startsWith("[") ||
150                    (ruleString.indexOf(':') != -1))
151            {
152               ruleType=RuleType.IPv6;
153            }
154            else
155            {
156                int wildCount=0;
157                String[] s = ruleString.split("\\.", -1);
158                //Try to figure out how many wildcards and if the rule is
159                // hostname (can't begin with digit) or ipv4 address.
160                //Default to IPv4 ruletype.
161                ruleType=RuleType.HOST;
162                for (String value : s) {
163                    if (value.equals("*")) {
164                        wildCount++;
165                        continue;
166                    }
167                    //Looks like an ipv4 address
168                    if (Character.isDigit(value.charAt(0))) {
169                        ruleType = RuleType.IPv4;
170                        break;
171                    }
172                }
173                //All wildcards (*.*.*.*)
174                if(wildCount == s.length)
175                {
176                    ruleType=RuleType.ALLWILDCARD;
177                }
178            }
179        }
180    
181        /**
182         * The rule string is an IPv4 rule. Build both the prefix
183         * mask array and rule mask from the string.
184         * @param rule The rule string containing the IPv4 rule.
185         * @throws ConfigException If the rule string is not a valid
186         *         IPv4 rule.
187         */
188        private void processIpv4(String rule)
189        throws ConfigException {
190            String[] s = rule.split("/", -1);
191            this.ruleMask=new byte[IN4ADDRSZ];
192            this.prefixMask=new byte[IN4ADDRSZ];
193            prefixMask(processPrefix(s,IPV4MAXPREFIX));
194            processIPv4Subnet((s.length == 0) ? rule : s[0]);
195        }
196    
197        /**
198         * The rule string is all wildcards. Set both address wildcard
199         * bitmask and hostname wildcard array.
200         * @param rule The rule string containing all wildcards.
201         */
202        private void processAllWilds(String rule)
203        {
204            String s[]=rule.split("\\.", -1);
205            if(s.length == IN4ADDRSZ)
206            {
207                for(int i=0;i<IN4ADDRSZ;i++)
208                    wildCard.set(i);
209            }
210            hostName=rule.split("\\.", -1);
211        }
212    
213        /**
214         * Examine the rule string of a host pattern and set the
215         * host pattern from the rule.
216         * @param rule The rule string to examine.
217         * @throws ConfigException If the rule string is not a valid
218         *         host pattern rule.
219         */
220        private void processHostPattern(String rule)
221        throws ConfigException
222        {
223            //quick check for invalid chars like " "
224            String s[]=rule.split("^[0-9a-zA-z-.]+");
225            if(s.length > 0)
226            {
227                Message message =
228                    ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
229                throw new ConfigException(message);
230            }
231            hostPattern=rule;
232        }
233    
234        /**
235         * Examine rule string and build a hostname string array
236         * of its parts.
237         * @param rule The rule string.
238         * @throws ConfigException If the rule string is not a valid
239         *         host name.
240         */
241        private void processHost(String rule)
242        throws ConfigException
243        {
244            //Note that '*' is valid in host rule
245            String s[]=rule.split("^[0-9a-zA-z-.*]+");
246            if(s.length > 0)
247            {
248                Message message =
249                    ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
250                throw new ConfigException(message);
251            }
252            hostName=rule.split("\\.", -1);
253        }
254    
255        /**
256         * Build the prefix mask of prefix len bits set in the array.
257         * @param prefix The len of the prefix to use.
258         */
259        private void prefixMask(int prefix)
260        {
261            int i;
262            for( i=0;prefix > 8 ; i++)
263            {
264                this.prefixMask[i] = (byte) 0xff;
265                prefix -= 8;
266            }
267            this.prefixMask[i] = (byte) ((0xff) << (8 - prefix));
268        }
269    
270        /**
271         * Examine the subnet part of a rule string and build a
272         * byte array representation of it.
273         * @param subnet The subnet string part of the rule.
274         * @throws ConfigException If the subnet string is not a valid
275         *         IPv4 subnet string.
276         */
277        private void  processIPv4Subnet(String subnet)
278        throws ConfigException {
279            String[] s = subnet.split("\\.", -1);
280            try {
281                //Make sure we have four parts
282                if(s.length != IN4ADDRSZ) {
283                    Message message =
284                        ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
285                    throw new ConfigException(message);
286                }
287                for(int i=0; i < IN4ADDRSZ; i++)
288                {
289                    String quad=s[i].trim();
290                    if(quad.equals("*"))
291                        wildCard.set(i) ; //see wildcard mark bitset
292                    else
293                    {
294                        long val=Integer.parseInt(quad);
295                        //must be between 0-255
296                        if((val < 0) ||  (val > 0xff))
297                        {
298                            Message message =
299                                ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
300                            throw new ConfigException(message);
301                        }
302                        ruleMask[i] = (byte) (val & 0xff);
303                    }
304                }
305            } catch (NumberFormatException nfex)
306            {
307                Message message =
308                    ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
309                throw new ConfigException(message);
310            }
311        }
312    
313        /**
314         * Examine rule string for correct prefix usage.
315         * @param s The string array with rule string add and prefix
316         *          strings.
317         * @param maxPrefix The max value the prefix can be.
318         * @return The prefix integer value.
319         * @throws ConfigException If the string array and prefix
320         *         are not valid.
321         */
322        private int  processPrefix(String[] s, int maxPrefix)
323        throws ConfigException {
324            int prefix=maxPrefix;
325            try {
326                //can only have one prefix value and a subnet string
327                if((s.length  < 1) || (s.length > 2) )
328                {
329                    Message message =
330                        ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
331                    throw new ConfigException(message);
332                }
333                else  if(s.length == 2)
334                {
335                    //can't have wildcard with a prefix
336                    if(s[0].indexOf('*') > -1)
337                    {
338                        Message message =
339                            ERR_ADDRESSMASK_WILDCARD_DECODE_ERROR.get();
340                        throw new ConfigException(message);
341                    }
342                    prefix = Integer.parseInt(s[1]);
343                }
344                //must be between 0-maxprefix
345                if((prefix < 0) || (prefix > maxPrefix))
346                {
347                    Message message =
348                        ERR_ADDRESSMASK_PREFIX_DECODE_ERROR.get();
349                    throw new ConfigException(message);
350                }
351            }
352            catch(NumberFormatException nfex)
353            {
354                Message msg = ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
355                throw new ConfigException(msg);
356            }
357            return prefix;
358        }
359    
360    
361        /**
362         * Decodes the provided string as an address mask.
363         *
364         * @param  maskString  The string to decode as an address mask.
365         *
366         * @return  AddressMask  The address mask decoded from the
367         *                       provided string.
368         *
369         * @throws  ConfigException  If the provided string cannot be
370         *                           decoded as an address mask.
371         */
372    
373    
374        public static AddressMask decode(String maskString)
375        throws ConfigException {
376            return new AddressMask(maskString);
377        }
378    
379        /**
380         * Indicates whether provided address or hostname matches one of
381         * the address masks in the provided array.
382         *
383         * @param remoteAddr The remote address byte array.
384         * @param remoteName The remote host name string.
385         * @param masks      An array of address masks to check.
386         * @return <CODE>true</CODE> if the provided address or hostname
387         *          does match one of the given address masks, or
388         *         <CODE>false</CODE> if it does not.
389         */
390        public  static boolean maskListContains(byte[] remoteAddr,
391                                                String remoteName,
392                                                AddressMask[] masks)
393        {
394            for (AddressMask mask : masks) {
395                if(mask.match(remoteAddr, remoteName))
396                    return true;
397            }
398            return false;
399        }
400    
401        /**
402         * Retrieves a string representation of this address mask.
403         *
404         * @return  A string representation of this address mask.
405         */
406        public String toString()
407        {
408            return ruleString;
409        }
410    
411        /**
412         * Main match function that determines which rule-type match
413         * function to use.
414         * @param remoteAddr The remote client address byte array.
415         * @param remoteName The remote client host name.
416         * @return <CODE>true</CODE>if one of the match functions found
417         *         a match or <CODE>false</CODE>if not.
418         */
419        private boolean match(byte[] remoteAddr, String remoteName)
420        {
421            boolean ret=false;
422    
423            switch(ruleType) {
424            case IPv6:
425            case IPv4:
426                //this Address mask is an IPv4 rule
427                ret=matchAddress(remoteAddr);
428                break;
429    
430            case HOST:
431                // HOST rule use hostname
432                ret=matchHostName(remoteName);
433                break;
434    
435            case HOSTPATTERN:
436                //HOSTPATTERN rule
437                ret=matchPattern(remoteName);
438                break;
439    
440            case ALLWILDCARD:
441                //first try  ipv4 addr match, then hostname
442                ret=matchAddress(remoteAddr);
443                if(!ret)
444                    ret=matchHostName(remoteName);
445                break;
446            }
447            return ret;
448        }
449    
450        /**
451         * Try to match remote host name string against the pattern rule.
452         * @param remoteHostName The remote client host name.
453         * @return <CODE>true</CODE>if the remote host name matches or
454         *         <CODE>false</CODE>if not.
455         */
456        private boolean matchPattern(String remoteHostName) {
457            int len=remoteHostName.length() - hostPattern.length();
458            return len > 0 && remoteHostName.regionMatches(true, len,
459                    hostPattern, 0, hostPattern.length());
460        }
461    
462        /**
463         * Try to match remote client host name against rule host name.
464         * @param remoteHostName The remote host name string.
465         * @return <CODE>true</CODE>if the remote client host name matches
466         *         <CODE>false</CODE> if it does not.
467         */
468        private boolean matchHostName(String remoteHostName) {
469            String[] s = remoteHostName.split("\\.", -1);
470            if(s.length != hostName.length)
471                return false;
472            if(ruleType == RuleType.ALLWILDCARD)
473                return true;
474            for(int i=0;i<s.length;i++)
475            {
476                if(!hostName[i].equals("*")) //skip if wildcard
477                {
478                    if(!s[i].equalsIgnoreCase(hostName[i]))
479                        return false;
480                }
481            }
482            return true;
483        }
484    
485    
486        /**
487         * Try to match remote client address using prefix mask and
488         * rule mask.
489         * @param remoteMask The byte array with remote client address.
490         * @return <CODE>true</CODE> if remote client address matches or
491         *         <CODE>false</CODE>if not.
492         */
493        private boolean matchAddress(byte[] remoteMask)
494        {
495            if(prefixMask== null)
496                return false;
497            if(remoteMask.length != prefixMask.length)
498                return false;
499            if(ruleType  == RuleType.ALLWILDCARD)
500                return true;
501            for(int i=0;i < prefixMask.length; i++)
502            {
503                if(!wildCard.get(i))
504                {
505                    if((ruleMask[i] & prefixMask[i]) !=
506                        (remoteMask[i] & prefixMask[i]))
507                        return false;
508                }
509            }
510            return true;
511        }
512    
513        /**
514         * The rule string is an IPv6 rule. Build both the prefix
515         * mask array and rule mask from the string.
516         *
517         * @param rule The rule string containing the IPv6 rule.
518         * @throws ConfigException If the rule string is not a valid
519         *         IPv6 rule.
520         */
521         private void processIPv6(String rule) throws ConfigException {
522            String[] s = rule.split("/", -1);
523            InetAddress addr;
524            try {
525                addr = InetAddress.getByName(s[0]);
526            } catch (UnknownHostException ex) {
527                Message message =
528                    ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
529                throw new ConfigException(message);
530            }
531            if(addr instanceof Inet6Address) {
532                this.ruleType=RuleType.IPv6;
533                Inet6Address addr6 = (Inet6Address) addr;
534                this.ruleMask=addr6.getAddress();
535                this.prefixMask=new byte[IN6ADDRSZ];
536                prefixMask(processPrefix(s,IPV6MAXPREFIX));
537            } else {
538               //The address might be an IPv4-compat address.
539               //Throw an error if the rule has a prefix.
540                if(s.length == 2) {
541                    Message message =
542                        ERR_ADDRESSMASK_FORMAT_DECODE_ERROR.get();
543                    throw new ConfigException(message);
544                }
545                this.ruleMask=addr.getAddress();
546                this.ruleType=RuleType.IPv4;
547                this.prefixMask=new byte[IN4ADDRSZ];
548                prefixMask(processPrefix(s,IPV4MAXPREFIX));
549            }
550        }
551    }
552    
553