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    package org.opends.server.authorization.dseecompat;
029    
030    import org.opends.messages.Message;
031    
032    import static org.opends.server.loggers.ErrorLogger.*;
033    import static org.opends.messages.AccessControlMessages.*;
034    import static org.opends.server.authorization.dseecompat.Aci.*;
035    import static org.opends.server.loggers.debug.DebugLogger.*;
036    import static org.opends.server.util.StaticUtils.*;
037    
038    import org.opends.server.loggers.debug.DebugTracer;
039    
040    import java.net.InetAddress;
041    import java.util.LinkedList;
042    import java.util.regex.Matcher;
043    import java.util.regex.Pattern;
044    import org.opends.server.types.DebugLogLevel;
045    
046    /**
047     * This class implements the dns bind rule keyword.
048     */
049    public class DNS implements KeywordBindRule {
050      /**
051       * The tracer object for the debug logger.
052       */
053      private static final DebugTracer TRACER = getTracer();
054    
055    
056        /*
057         * List of patterns to match against.
058         */
059        LinkedList<String> patterns=null;
060    
061        /*
062         * The enumeration representing the bind rule type of the DNS rule.
063         */
064        private EnumBindRuleType type=null;
065    
066        /*
067         *  Regular expression group used to match a dns rule.
068         */
069        private static final String valueRegex = "([a-zA-Z0-9\\.\\-\\*]+)";
070    
071        /*
072         * Regular expression group used to match one or more DNS values.
073         */
074        private static final String valuesRegExGroup =
075                valueRegex + ZERO_OR_MORE_WHITESPACE +
076                "(," +  ZERO_OR_MORE_WHITESPACE  +  valueRegex  +  ")*";
077    
078        /**
079         * Create a class representing a dns bind rule keyword.
080         * @param patterns List of dns patterns to match against.
081         * @param type An enumeration representing the bind rule type.
082         */
083        DNS(LinkedList<String> patterns, EnumBindRuleType type) {
084            this.patterns=patterns;
085            this.type=type;
086        }
087    
088        /**
089         * Decode an string representing a dns bind rule.
090         * @param expr A string representation of the bind rule.
091         * @param type  An enumeration representing the bind rule type.
092         * @return  A keyword bind rule class that can be used to evaluate
093         * this bind rule.
094         * @throws AciException  If the expression string is invalid.
095         */
096        public static DNS decode(String expr,  EnumBindRuleType type)
097        throws AciException
098        {
099            if (!Pattern.matches(valuesRegExGroup, expr)) {
100                Message message = WARN_ACI_SYNTAX_INVALID_DNS_EXPRESSION.get(expr);
101                throw new AciException(message);
102            }
103            LinkedList<String>dns=new LinkedList<String>();
104            int valuePos = 1;
105            Pattern valuePattern = Pattern.compile(valueRegex);
106            Matcher valueMatcher = valuePattern.matcher(expr);
107            while (valueMatcher.find()) {
108                String hn=valueMatcher.group(valuePos);
109                String[] hnArray=hn.split("\\.", -1);
110                for(int i=1, n=hnArray.length; i < n; i++) {
111                    if(hnArray[i].equals("*")) {
112                        Message message =
113                            WARN_ACI_SYNTAX_INVALID_DNS_WILDCARD.get(expr);
114                        throw new AciException(message);
115                    }
116                }
117    
118                // If the provided hostname does not contain any wildcard
119                // characters, then it must be the canonical hostname for the
120                // associated IP address.  If it is not, then it will not match the
121                // intended target, and we should generate a warning message to let
122                // the administrator know about it.  If the provided value does not
123                // match the canonical name for the associated IP address, and the
124                // given hostname is "localhost", then we should treat it specially
125                // and also match the canonical hostname.  This is necessary because
126                // "localhost" is likely to be very commonly used in these kinds of
127                // rules and on some systems the canonical representation is
128                // configured to be "localhost.localdomain" which may not be known
129                // to the administrator.
130                if (hn.indexOf("*") < 0)
131                {
132                  try
133                  {
134                    for (InetAddress addr : InetAddress.getAllByName(hn))
135                    {
136                      String canonicalName = addr.getCanonicalHostName();
137                      if (! hn.equalsIgnoreCase(canonicalName))
138                      {
139                        if (hn.equalsIgnoreCase("localhost") &&
140                            (! dns.contains(canonicalName)))
141                        {
142                          dns.add(canonicalName);
143    
144                          Message message =
145                            WARN_ACI_LOCALHOST_DOESNT_MATCH_CANONICAL_VALUE.
146                                get(expr, hn, canonicalName);
147                          logError(message);
148                        }
149                        else
150                        {
151                          Message message =
152                            WARN_ACI_HOSTNAME_DOESNT_MATCH_CANONICAL_VALUE.
153                                get(expr, hn, addr.getHostAddress(),
154                                    addr.getCanonicalHostName());
155                          logError(message);
156                        }
157                      }
158                    }
159                  }
160                  catch (Exception e)
161                  {
162                    if (debugEnabled())
163                    {
164                      TRACER.debugCaught(DebugLogLevel.ERROR, e);
165                    }
166    
167                    Message message = WARN_ACI_ERROR_CHECKING_CANONICAL_HOSTNAME.
168                        get(hn, expr, getExceptionMessage(e));
169                    logError(message);
170                  }
171                }
172    
173                dns.add(hn);
174            }
175            return new DNS(dns, type);
176        }
177    
178        /**
179         * Performs evaluation of dns keyword bind rule using the provided
180         * evaluation context.
181         * @param evalCtx  An evaluation context to use in the evaluation.
182         * @return An enumeration evaluation result.
183         */
184        public EnumEvalResult evaluate(AciEvalContext evalCtx) {
185            EnumEvalResult matched=EnumEvalResult.FALSE;
186            String[] remoteHost = evalCtx.getHostName().split("\\.", -1);
187            for(String p : patterns) {
188              String[] pat = p.split("\\.", -1);
189              if(evalHostName(remoteHost, pat)) {
190                  matched=EnumEvalResult.TRUE;
191                  break;
192              }
193            }
194            return matched.getRet(type, false);
195        }
196    
197        /**
198         * Checks an array containing the remote client's hostname against
199         * patterns specified in the bind rule expression. Wild-cards are
200         * only permitted in the leftmost field and the rest of the domain
201         * name array components must match. A single wild-card matches any
202         * hostname.
203         * @param remoteHostName  Array containing components of the remote clients
204         * hostname (split on ".").
205         * @param pat  An array containing the pattern specified in
206         * the bind rule expression. The first array slot may be a wild-card "*".
207         * @return  True if the remote hostname matches the pattern.
208         */
209          boolean evalHostName(String[] remoteHostName, String[] pat) {
210          boolean wildCard=pat[0].equals("*");
211          //Check if there is a single wild-card.
212          if(pat.length == 1 && wildCard)
213            return true;
214          int remoteHnIndex=remoteHostName.length-pat.length;
215          if(remoteHnIndex < 0)
216            return false;
217          int patternIndex=0;
218          if(!wildCard)
219              remoteHnIndex=0;
220          else {
221              patternIndex=1;
222              remoteHnIndex++;
223          }
224          for(int i=remoteHnIndex ;i<remoteHostName.length;i++)
225                if(!pat[patternIndex++].equalsIgnoreCase(remoteHostName[i]))
226                    return false;
227          return true;
228        }
229    }